/**
 * A core util class to bring Draggable behavior to a Component. This class is specifically designed only for
 * absolutely positioned elements starting from top: 0, left: 0. The initialOffset can then be set via configuration
 * to have the elements in a different position.
 */
Ext.define('Ext.util.Draggable', {
    isDraggable: true,

    mixins: [
        'Ext.mixin.Observable'
    ],

    requires: [
        'Ext.util.Translatable'
    ],

    /**
     * @event dragstart
     * @preventable initDragStart
     * Fires whenever the component starts to be dragged
     * @param {Ext.util.Draggable} this
     * @param {Ext.event.Event} e the event object
     * @param {Number} offsetX The current offset value on the x axis
     * @param {Number} offsetY The current offset value on the y axis
     */

    /**
     * @event drag
     * Fires whenever the component is dragged
     * @param {Ext.util.Draggable} this
     * @param {Ext.event.Event} e the event object
     * @param {Number} offsetX The new offset value on the x axis
     * @param {Number} offsetY The new offset value on the y axis
     */

    /**
     * @event dragend
     * Fires whenever the component is dragged
     * @param {Ext.util.Draggable} this
     * @param {Ext.event.Event} e the event object
     * @param {Number} offsetX The current offset value on the x axis
     * @param {Number} offsetY The current offset value on the y axis
     */

    config: {
        cls: Ext.baseCSSPrefix + 'draggable',

        draggingCls: Ext.baseCSSPrefix + 'dragging',

        element: null,

        constraint: 'container',

        disabled: null,

        /**
         * @cfg {String} direction
         * Possible values: 'vertical', 'horizontal', or 'both'
         * @accessor
         */
        direction: 'both',

        /**
         * @cfg {Object/Number} initialOffset
         * The initial draggable offset.  When specified as Number,
         * both x and y will be set to that value.
         */
        initialOffset: {
            x: 0,
            y: 0
        },

        translatable: {}
    },

    DIRECTION_BOTH: 'both',

    DIRECTION_VERTICAL: 'vertical',

    DIRECTION_HORIZONTAL: 'horizontal',

    defaultConstraint: {
        min: { x: -Infinity, y: -Infinity },
        max: { x: Infinity, y: Infinity }
    },

    containerWidth: 0,

    containerHeight: 0,

    width: 0,

    height: 0,

    /**
     * Creates new Draggable.
     * @param {Object} config The configuration object for this Draggable.
     */
    constructor: function(config) {
        var element;

        this.extraConstraint = {};

        this.initialConfig = config;

        this.offset = {
            x: 0,
            y: 0
        };

        this.listeners = {
            dragstart: 'onDragStart',
            drag     : 'onDrag',
            dragend  : 'onDragEnd',
            resize   : 'onElementResize',
            touchstart : 'onPress',
            touchend   : 'onRelease',
            scope: this
        };

        if (config && config.element) {
            element = config.element;
            delete config.element;

            this.setElement(element);
        }

        return this;
    },

    applyElement: function(element) {
        if (!element) {
            return;
        }

        return Ext.get(element);
    },

    updateElement: function(element) {
        element.on(this.listeners);

        this.initConfig(this.initialConfig);
    },

    updateInitialOffset: function(initialOffset) {
        if (typeof initialOffset == 'number') {
            initialOffset = {
                x: initialOffset,
                y: initialOffset
            };
        }

        var offset = this.offset,
            x, y;

        offset.x = x = initialOffset.x;
        offset.y = y = initialOffset.y;

        this.getTranslatable().translate(x, y);
    },

    updateCls: function(cls) {
        this.getElement().addCls(cls);
    },

    applyTranslatable: function(translatable, currentInstance) {
        translatable = Ext.factory(translatable, Ext.util.Translatable, currentInstance);
        if (translatable) {
            translatable.setElement(this.getElement());
        }

        return translatable;
    },

    setExtraConstraint: function(constraint) {
        this.extraConstraint = constraint || {};

        this.refreshConstraint();

        return this;
    },

    addExtraConstraint: function(constraint) {
        Ext.merge(this.extraConstraint, constraint);

        this.refreshConstraint();

        return this;
    },

    applyConstraint: function(newConstraint) {
        this.currentConstraint = newConstraint;

        if (!newConstraint) {
            newConstraint = this.defaultConstraint;
        }

        if (newConstraint === 'container') {
            return Ext.merge(this.getContainerConstraint(), this.extraConstraint);
        }

        return Ext.merge({}, this.extraConstraint, newConstraint);
    },

    updateConstraint: function() {
        this.refreshOffset();
    },

    getContainerConstraint: function() {
        var container = this.getContainer(),
            element = this.getElement();

        if (!container || !element.dom) {
            return this.defaultConstraint;
        }

        return {
            min: { x: 0, y: 0 },
            max: { x: this.containerWidth - this.width, y: this.containerHeight - this.height }
        };
    },

    getContainer: function() {
        var container = this.container;

        if (!container) {
            container = this.getElement().getParent();

            if (container) {
                this.container = container;

                container.on({
                    resize: 'onContainerResize',
                    destroy: 'onContainerDestroy',
                    scope: this
                });
            }
        }

        return container;
    },

    onElementResize: function(element, info) {
        this.width = info.width;
        this.height = info.height;

        this.refresh();
    },

    onContainerResize: function(container, info) {
        this.containerWidth = info.width;
        this.containerHeight = info.height;

        this.refresh();
    },

    onContainerDestroy: function() {
        delete this.container;
        delete this.containerSizeMonitor;
    },

    detachListeners: function() {
        this.getElement().un(this.listeners);
    },

    isAxisEnabled: function(axis) {
        var direction = this.getDirection();

        if (axis === 'x') {
            return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_HORIZONTAL);
        }

        return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_VERTICAL);
    },

    onPress: function(e) {
        this.fireAction('touchstart', [this, e]);
    },

    onRelease: function(e) {
        this.fireAction('touchend', [this, e]);
    },

    onDragStart: function(e) {
        if (this.getDisabled()) {
            return false;
        }
        var offset = this.offset;

        this.fireAction('dragstart', [this, e, offset.x, offset.y], this.initDragStart);
    },

    initDragStart: function(me, e, offsetX, offsetY) {
        this.dragStartOffset = {
            x: offsetX,
            y: offsetY
        };

        this.isDragging = true;

        this.getElement().addCls(this.getDraggingCls());
    },

    onDrag: function(e) {
        if (!this.isDragging) {
            return;
        }

        var startOffset = this.dragStartOffset;

        this.fireAction('drag', [this, e, startOffset.x + e.deltaX, startOffset.y + e.deltaY], this.doDrag);
    },

    doDrag: function(me, e, offsetX, offsetY) {
        me.setOffset(offsetX, offsetY);
    },

    onDragEnd: function(e) {
        if (!this.isDragging) {
            return;
        }

        this.onDrag(e);

        this.isDragging = false;

        this.getElement().removeCls(this.getDraggingCls());

        this.fireEvent('dragend', this, e, this.offset.x, this.offset.y);
    },

    setOffset: function(x, y, animation) {
        var currentOffset = this.offset,
            constraint = this.getConstraint(),
            minOffset = constraint.min,
            maxOffset = constraint.max,
            min = Math.min,
            max = Math.max;

        if (this.isAxisEnabled('x') && typeof x == 'number') {
            x = min(max(x, minOffset.x), maxOffset.x);
        }
        else {
            x = currentOffset.x;
        }

        if (this.isAxisEnabled('y') && typeof y == 'number') {
            y = min(max(y, minOffset.y), maxOffset.y);
        }
        else {
            y = currentOffset.y;
        }

        currentOffset.x = x;
        currentOffset.y = y;

        this.getTranslatable().translate(x, y, animation);
    },

    getOffset: function() {
        return this.offset;
    },

    refreshConstraint: function() {
        this.setConstraint(this.currentConstraint);
    },

    refreshOffset: function() {
        var offset = this.offset;

        this.setOffset(offset.x, offset.y);
    },

    refresh: function() {
        this.refreshConstraint();
        this.getTranslatable().refresh();
        this.refreshOffset();
    },

    /**
     * Enable the Draggable.
     * @return {Ext.util.Draggable} This Draggable instance
     */
    enable: function() {
        return this.setDisabled(false);
    },

    /**
     * Disable the Draggable.
     * @return {Ext.util.Draggable} This Draggable instance
     */
    disable: function() {
        return this.setDisabled(true);
    },

    destroy: function() {
        var translatable = this.getTranslatable();

        var element = this.getElement();
        if (element && !element.isDestroyed) {
            element.removeCls(this.getCls());
        }

        this.detachListeners();

        if (translatable) {
            translatable.destroy();
        }
    }

}, function() {
    //<deprecated product=touch since=2.0>
    this.override({
        constructor: function(config) {
            if (config && config.constrain) {
                //<debug warn>
                Ext.Logger.deprecate("'constrain' config is deprecated, please use 'contraint' instead");
                //</debug>
                config.contraint = config.constrain;
                delete config.constrain;
            }

            return this.callOverridden(arguments);
        }
    });
    //</deprecated>
});


/**
 * Copyright (c) 2016 Apentle.com
 *
 * This source code is licensed under the MIT-style license found in
 * the LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

const {add} = require('redux-actions-hub');

/**
 * addActions - add actions util
 *
 * @param  {array|object} actions   actions to add
 * @returns {undefined}
 */
function addActions(actions) {
  if (typeof actions === 'string') {
    add(actions);
  } else if (Array.isArray(actions)) {
    actions.forEach(addActions);
  } else if (typeof actions === 'object') {
    for (var type in actions) {
      add(type, actions[type]);
    }
  }
}

module.exports = addActions;


/**
*
*	COMPUTE: indexspace
*
*
*	DESCRIPTION:
*		- Generates a linearly spaced index array from a subsequence string.
*
*
*	NOTES:
*		[1]
*
*
*	TODO:
*		[1]
*
*
*	LICENSE:
*		MIT
*
*	Copyright (c) 2015. Athan Reines.
*
*
*	AUTHOR:
*		Athan Reines. kgryte@gmail.com. 2015.
*
*/

'use strict';

// MODULES //

var isString = require( 'validate.io-string-primitive' ),
	isNonNegativeInteger = require( 'validate.io-nonnegative-integer' );

// VARIABLES //

var re = /^(?:(?:-(?=\d+))?\d*|end(?:-\d+|\/\d+)?):(?:(?:-(?=\d+))?\d*|end(?:-\d+|\/\d+)?)(?:\:(?=-?\d*)(?:-?\d+)?)?$/;

/**
*	^(...)
*	=> require that the string begin with either a digit (+ or -), an `end` keyword, or a colon
*
*	(?:(?:-(?=\d+))?\d*|end(?:-?\d+|/\\d+)?)
*	=> match but do not capture
*		(?:-(?=\d+))?
*		=> match but do not capture a minus sign but only if followed by one or more digits
*		\d*
*		=> 0 or more digits
*		|
*		=> OR
*		end(?:-\d+|/\d+)?
*		=> the word `end` exactly, which may be followed by either a minus sign and 1 or more digits or a division sign and 1 or more digits
*
*	:
*	=> match a colon exactly
*
*	(?:(?:-(?=\d+))?\d*|end(?:-\d+|/\\d+)?)
*	=> same as above
*
*	(?:\:(?=-?\d*)(?:-?\d+)?)?
*	=> match but do not capture
*		\:(?=-?\d*)
*		=> a colon but only if followed by either a possible minus sign and 0 or more digits
*		(?:-?\d+)?
*		=> match but do not capture a possible minus sign and 1 or more digits
*
*	$
*	=> require that the string end with either a digit, `end` keyword, or a colon.
*
*
* Examples:
*	-	:
*	-	::
*	-	4:
*	-	:4
*	-	::-1
*	-	3::-1
*	-	:10:2
*	-	1:3:1
*	-	9:1:-3
*	-	1:-1
*	-	:-1
*	-	-5:
*	-	1:-5:2
*	-	-9:10:1
*	-	-9:-4:2
*	-	-4:-9:-2
*	-	1:end:2
*	-	:end/2
*	-	end/2:end:5
*/

var reEnd = /^end/,
	reMatch = /(-|\/)(?=\d+)(\d+)?$/;

// INDEXSPACE

/**
* FUNCTION: indexspace( str, len )
*	Generates a linearly spaced index array from a subsequence string.
*
* @param {String} str - subsequence string
* @param {Number} len - reference array length
* @returns {Number[]} array of indices
*/
function indexspace( str, len ) {
	var x1,
		x2,
		tmp,
		inc,
		arr;
	if ( !isString( str ) || !re.test( str ) ) {
		throw new Error( 'indexspace()::invalid input argument. Invalid subsequence syntax. Please consult documentation. Value: `' + str + '`.' );
	}
	if ( !isNonNegativeInteger( len ) ) {
		throw new TypeError( 'indexspace()::invalid input argument. Reference array length must be a nonnegative integer. Value: `' + len + '`.' );
	}
	if ( !len ) {
		return [];
	}
	str = str.split( ':' );
	x1 = str[ 0 ];
	x2 = str[ 1 ];

	if ( str.length === 2 ) {
		inc = 1;
	} else {
		inc = parseInt( str[ 2 ], 10 );
	}
	// Handle zero increment...
	if ( inc === 0 ) {
		throw new Error( 'indexspace()::invalid syntax. Increment must be an integer not equal to 0. Value: `' + inc + '`.' );
	}

	// START //

	// Handle use of 'end' keyword...
	if ( reEnd.test( x1 ) ) {
		tmp = x1.match( reMatch );
		if ( tmp ) {
			if ( tmp[ 1 ] === '-' ) {
				x1 = len - 1 - parseInt( tmp[ 2 ], 10 );
				if ( x1 < 0 ) {
					// WARNING: forgive the user for exceeding the range bounds...
					x1 = 0;
				}
			} else {
				x1 = (len-1) / parseInt( tmp[ 2 ], 10 );
				x1 = Math.ceil( x1 );
			}
		} else {
			x1 = len - 1;
		}
	} else {
		x1 = parseInt( x1, 10 );

		// Handle empty index...
		if ( x1 !== x1 ) {
			// :-?\d*:-?\d+
			if ( inc < 0 ) {
				// Max index:
				x1 = len - 1;
			} else {
				// Min index:
				x1 = 0;
			}
		}
		// Handle negative index...
		else if ( x1 < 0 ) {
			x1 = len + x1; // len-x1
			if ( x1 < 0 ) {
				// WARNING: forgive the user for exceeding index bounds...
				x1 = 0;
			}
		}
		// Handle exceeding bounds...
		else if ( x1 >= len ) {
			return [];
		}
	}

	// END //

	// NOTE: here, we determine an inclusive `end` value; i.e., the last acceptable index value.

	// Handle use of 'end' keyword...
	if ( reEnd.test( x2 ) ) {
		tmp = x2.match( reMatch );
		if ( tmp ) {
			if ( tmp[ 1 ] === '-' ) {
				x2 = len - 1 - parseInt( tmp[ 2 ], 10 );
				if ( x2 < 0 ) {
					// WARNING: forgive the user for exceeding the range bounds...
					x2 = 0;
				}
			} else {
				x2 = (len-1) / parseInt( tmp[ 2 ], 10 );
				x2 = Math.ceil( x2 ) - 1;
			}
		} else {
			x2 = len - 1;
		}
	} else {
		x2 = parseInt( x2, 10 );

		// Handle empty index...
		if ( x2 !== x2 ) {
			// -?\d*::-?\d+
			if ( inc < 0 ) {
				// Min index:
				x2 = 0;
			} else {
				// Max index:
				x2 = len - 1;
			}
		}
		// Handle negative index...
		else if ( x2 < 0 ) {
			x2 = len + x2; // len-x2
			if ( x2 < 0 ) {
				// WARNING: forgive the user for exceeding index bounds...
				x2 = 0;
			}
			if ( inc > 0 ) {
				x2 = x2 - 1;
			}
		}
		// Handle positive index...
		else {
			if ( inc < 0 ) {
				x2 = x2 + 1;
			}
			else if ( x2 >= len ) {
				x2 = len - 1;
			}
			else {
				x2 = x2 - 1;
			}
		}
	}

	// INDICES //

	arr = [];
	if ( inc < 0 ) {
		if ( x2 > x1 ) {
			return arr;
		}
		while ( x1 >= x2 ) {
			arr.push( x1 );
			x1 += inc;
		}
	} else {
		if ( x1 > x2 ) {
			return arr;
		}
		while ( x1 <= x2 ) {
			arr.push( x1 );
			x1 += inc;
		}
	}
	return arr;
} // end FUNCTION indexspace()

// EXPORTS //

module.exports = indexspace;


//@tag dom,core
//@define Ext.EventManager
//@define Ext.core.EventManager
//@require Ext.Loader

/**
 * @class Ext.EventManager
 *
 * This object has been deprecated in Sencha Touch 2.0.0. Please refer to the method documentation for specific alternatives.
 *
 * @deprecated 2.0.0
 * @singleton
 * @private
 */

//<deprecated product=touch since=2.0>
Ext.ns('Ext.core');
Ext.core.EventManager =
Ext.EventManager = {
    /**
     * Appends an event handler to an element.  The shorthand version {@link #on} is equivalent.  Typically you will
     * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
     * @param {String/HTMLElement} el The HTML element or `id` to assign the event handler to.
     * @param {String} eventName The name of the event to listen for.
     * @param {Function} handler The handler function the event invokes. This function is passed
     * the following parameters:
     * @param {Ext.EventObject} handler.evt The {@link Ext.EventObject EventObject} describing the event.
     * @param {Ext.Element} handler.t The {@link Ext.Element Element} which was the target of the event.
     * Note that this may be filtered by using the `delegate` option.
     * @param {Object} handler.o The options object from the addListener call.
     * @param {Object} scope (optional) The scope (`this` reference) in which the handler function is executed. __Defaults to the Element__.
     * @param {Object} options (optional) An object containing handler configuration properties.
     * This may contain any of the following properties:
     * @param {Object} [options.scope] The scope (`this` reference) in which the handler function is executed. __Defaults to the Element__.
     * @param {String} [options.delegate] A simple selector to filter the target or look for a descendant of the target.
     * @param {Boolean} [options.stopEvent] `true` to stop the event. That is stop propagation, and prevent the default action.
     * @param {Boolean} [options.preventDefault] `true` to prevent the default action.
     * @param {Boolean} [options.stopPropagation] `true` to prevent event propagation.
     * @param {Boolean} [options.normalized] `false` to pass a browser event to the handler function instead of an Ext.EventObject.
     * @param {Number} [options.delay] The number of milliseconds to delay the invocation of the handler after the event fires.
     * @param {Boolean} [options.single] `true` to add a handler to handle just the next firing of the event, and then remove itself.
     * @param {Number} [options.buffer] Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
     * by the specified number of milliseconds. If the event fires again within that time, the original
     * handler is _not_ invoked, but the new handler is scheduled in its place.
     * @param {Ext.Element} [options.target] Only call the handler if the event was fired on the target Element, _not_ if the event was bubbled up from a child node.
     *
     * See {@link Ext.Element#addListener} for examples of how to use these options.
     * @deprecated 2.0.0 Please use {@link Ext.dom.Element#addListener addListener} on an instance of Ext.Element instead.
     */
    addListener: function(element, eventName, fn, scope, options) {
        //<debug warn>
        Ext.Logger.deprecate("Ext.EventManager.addListener is deprecated, use addListener() directly from an instance of Ext.Element instead", 2);
        //</debug>
        element.on(eventName, fn, scope, options);
    },

    /**
     * Removes an event handler from an element.  The shorthand version {@link #un} is equivalent.  Typically
     * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
     * @param {String/HTMLElement} el The id or html element from which to remove the listener.
     * @param {String} eventName The name of the event.
     * @param {Function} fn The handler function to remove. __This must be a reference to the function passed into the {@link #addListener} call.__
     * @param {Object} scope If a scope (`this` reference) was specified when the listener was added,
     * then this must refer to the same object.
     * @deprecated 2.0.0 Please use {@link Ext.dom.Element#removeListener removeListener} on an instance of Ext.Element instead.
     */
    removeListener: function(element, eventName, fn, scope) {
        //<debug warn>
        Ext.Logger.deprecate("Ext.EventManager.removeListener is deprecated, use removeListener() directly from an instance of Ext.Element instead", 2);
        //</debug>
        element.un(eventName, fn, scope);
    },

    /**
     * Removes all event handers from an element.  Typically you will use {@link Ext.Element#clearListeners}
     * directly on an Element in favor of calling this version.
     * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
     * @deprecated 2.0.0 Please use {@link Ext.dom.Element#clearListeners clearListeners} on an instance of Ext.Element instead.
     */
    removeAll: function(element){
        //<debug warn>
        Ext.Logger.deprecate("Ext.EventManager.removeAll is deprecated, use clearListeners() directly from an instance of Ext.Element instead", 3);
        //</debug>
        Ext.get(element).clearListeners();
    },

    /**
     * Adds a listener to be notified when the document is ready (before `onload` and before images are loaded).
     * @removed 2.0.0 Please use {@link Ext#onReady onReady}
     */
    onDocumentReady: function() {
        //<debug warn>
        Ext.Logger.deprecate("Ext.EventManager.onDocumentReady has been removed, please use Ext.onReady instead", 3);
        //</debug>
    },

    /**
     * Adds a listener to be notified when the browser window is resized and provides resize event buffering (50 milliseconds),
     * passes new viewport width and height to handlers.
     * @param {Function} fn      The handler function the window resize event invokes.
     * @param {Object}   scope   The scope (`this` reference) in which the handler function executes. Defaults to the browser window.
     * @param {Boolean}  options Options object as passed to {@link Ext.Element#addListener}
     * @deprecated 2.0.0 Please listen to the {@link Ext.Viewport#event-resize resize} on Ext.Viewport instead.
     */
    onWindowResize: function(fn, scope, options) {
        //<debug warn>
        Ext.Logger.deprecate("Ext.EventManager.onWindowResize is deprecated, attach listener to Ext.Viewport instead, i.e: Ext.Viewport.on('resize', ...)", 2);
        //</debug>
        Ext.Viewport.on('resize', fn, scope, options);
    },

    onOrientationChange: function(fn, scope, options) {
        //<debug warn>
        Ext.Logger.deprecate("Ext.EventManager.onOrientationChange is deprecated, attach listener to Ext.Viewport instead, i.e: Ext.Viewport.on('orientationchange', ...)", 2);
        //</debug>
        Ext.Viewport.on('orientationchange', fn, scope, options);
    },

    unOrientationChange: function(fn, scope, options) {
        //<debug warn>
        Ext.Logger.deprecate("Ext.EventManager.unOrientationChange is deprecated, remove listener from Ext.Viewport instead, i.e: Ext.Viewport.un('orientationchange', ...)", 2);
        //</debug>
        Ext.Viewport.un('orientationchange', fn, scope, options);
    }
};

/**
* Appends an event handler to an element.  Shorthand for {@link #addListener}.
* @param {String/HTMLElement} el The html element or id to assign the event handler to.
* @param {String} eventName The name of the event to listen for.
* @param {Function} handler The handler function the event invokes.
* @param {Object} scope (optional) (`this` reference) in which the handler function executes. __Defaults to the Element__.
* @param {Object} options (optional) An object containing standard {@link #addListener} options
* @member Ext.EventManager
* @method on
* @deprecated 2.0.0 Please use {@link Ext.dom.Element#addListener addListener} on an instance of Ext.Element instead.
*/
Ext.EventManager.on = Ext.EventManager.addListener;

/**
 * Removes an event handler from an element.  Shorthand for {@link #removeListener}.
 * @param {String/HTMLElement} el The id or html element from which to remove the listener.
 * @param {String} eventName The name of the event.
 * @param {Function} fn The handler function to remove. __This must be a reference to the function passed into the {@link #on} call.__
 * @param {Object} scope If a scope (`this` reference) was specified when the listener was added,
 * then this must refer to the same object.
 * @member Ext.EventManager
 * @method un
 * @deprecated 2.0.0 Please use {@link Ext.dom.Element#removeListener removeListener} on an instance of Ext.Element instead.
 */
Ext.EventManager.un = Ext.EventManager.removeListener;
//</deprecated>


//@tag foundation,core
//@define Ext.String
//@require Ext.Version

/**
 * @class Ext.String
 *
 * A collection of useful static methods to deal with strings.
 * @singleton
 */

Ext.String = {
    trimRegex: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
    escapeRe: /('|\\)/g,
    formatRe: /\{(\d+)\}/g,
    escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,

    /**
     * Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
     * @param {String} value The string to encode.
     * @return {String} The encoded text.
     * @method
     */
    htmlEncode: (function() {
        var entities = {
            '&': '&',
            '>': '>',
            '<': '<',
            '"': '"'
        }, keys = [], p, regex;

        for (p in entities) {
            keys.push(p);
        }

        regex = new RegExp('(' + keys.join('|') + ')', 'g');

        return function(value) {
            return (!value) ? value : String(value).replace(regex, function(match, capture) {
                return entities[capture];
            });
        };
    })(),

    /**
     * Convert certain characters (&, <, >, and ") from their HTML character equivalents.
     * @param {String} value The string to decode.
     * @return {String} The decoded text.
     * @method
     */
    htmlDecode: (function() {
        var entities = {
            '&': '&',
            '>': '>',
            '<': '<',
            '"': '"'
        }, keys = [], p, regex;

        for (p in entities) {
            keys.push(p);
        }

        regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');

        return function(value) {
            return (!value) ? value : String(value).replace(regex, function(match, capture) {
                if (capture in entities) {
                    return entities[capture];
                } else {
                    return String.fromCharCode(parseInt(capture.substr(2), 10));
                }
            });
        };
    })(),

    /**
     * Appends content to the query string of a URL, handling logic for whether to place
     * a question mark or ampersand.
     * @param {String} url The URL to append to.
     * @param {String} string The content to append to the URL.
     * @return {String} The resulting URL.
     */
    urlAppend : function(url, string) {
        if (!Ext.isEmpty(string)) {
            return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
        }

        return url;
    },

    /**
     * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
     *
     *     @example
     *     var s = '  foo bar  ';
     *     alert('-' + s + '-'); // alerts "-  foo bar  -"
     *     alert('-' + Ext.String.trim(s) + '-'); // alerts "-foo bar-"
     *
     * @param {String} string The string to escape
     * @return {String} The trimmed string
     */
    trim: function(string) {
        return string.replace(Ext.String.trimRegex, "");
    },

    /**
     * Capitalize the given string.
     * @param {String} string
     * @return {String}
     */
    capitalize: function(string) {
        return string.charAt(0).toUpperCase() + string.substr(1);
    },

    /**
     * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
     * @param {String} value The string to truncate.
     * @param {Number} length The maximum length to allow before truncating.
     * @param {Boolean} word `true` to try to find a common word break.
     * @return {String} The converted text.
     */
    ellipsis: function(value, len, word) {
        if (value && value.length > len) {
            if (word) {
                var vs = value.substr(0, len - 2),
                index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
                if (index !== -1 && index >= (len - 15)) {
                    return vs.substr(0, index) + "...";
                }
            }
            return value.substr(0, len - 3) + "...";
        }
        return value;
    },

    /**
     * Escapes the passed string for use in a regular expression.
     * @param {String} string
     * @return {String}
     */
    escapeRegex: function(string) {
        return string.replace(Ext.String.escapeRegexRe, "\\$1");
    },

    /**
     * Escapes the passed string for ' and \.
     * @param {String} string The string to escape.
     * @return {String} The escaped string.
     */
    escape: function(string) {
        return string.replace(Ext.String.escapeRe, "\\$1");
    },

    /**
     * Utility function that allows you to easily switch a string between two alternating values.  The passed value
     * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
     * they are already different, the first value passed in is returned.  Note that this method returns the new value
     * but does not change the current string.
     *
     *     // alternate sort directions
     *     sort = Ext.String.toggle(sort, 'ASC', 'DESC');
     *
     *     // instead of conditional logic:
     *     sort = (sort == 'ASC' ? 'DESC' : 'ASC');
     *
     * @param {String} string The current string.
     * @param {String} value The value to compare to the current string.
     * @param {String} other The new value to use if the string already equals the first value passed in.
     * @return {String} The new value.
     */
    toggle: function(string, value, other) {
        return string === value ? other : value;
    },

    /**
     * Pads the left side of a string with a specified character.  This is especially useful
     * for normalizing number and date strings.  Example usage:
     *
     *     var s = Ext.String.leftPad('123', 5, '0');
     *     alert(s); // '00123'
     *
     * @param {String} string The original string.
     * @param {Number} size The total length of the output string.
     * @param {String} [character= ] (optional) The character with which to pad the original string (defaults to empty string " ").
     * @return {String} The padded string.
     */
    leftPad: function(string, size, character) {
        var result = String(string);
        character = character || " ";
        while (result.length < size) {
            result = character + result;
        }
        return result;
    },

    /**
     * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
     * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
     *
     *     var cls = 'my-class',
     *         text = 'Some text';
     *     var s = Ext.String.format('
{1}
', cls, text);
     *     alert(s); // '
Some text
'
     *
     * @param {String} string The tokenized string to be formatted.
     * @param {String...} values First param value to replace token `{0}`, then next
     * param to replace `{1}` etc.
     * @return {String} The formatted string.
     */
    format: function(format) {
        var args = Ext.Array.toArray(arguments, 1);
        return format.replace(Ext.String.formatRe, function(m, i) {
            return args[i];
        });
    },

    /**
     * Returns a string with a specified number of repetitions a given string pattern.
     * The pattern be separated by a different string.
     *
     *     var s = Ext.String.repeat('---', 4); // '------------'
     *     var t = Ext.String.repeat('--', 3, '/'); // '--/--/--'
     *
     * @param {String} pattern The pattern to repeat.
     * @param {Number} count The number of times to repeat the pattern (may be 0).
     * @param {String} sep An option string to separate each pattern.
     */
    repeat: function(pattern, count, sep) {
        for (var buf = [], i = count; i--; ) {
            buf.push(pattern);
        }
        return buf.join(sep || '');
    }
};

/**
 * Old alias to {@link Ext.String#htmlEncode}.
 * @deprecated Use {@link Ext.String#htmlEncode} instead.
 * @method
 * @member Ext
 * @alias Ext.String#htmlEncode
 */
Ext.htmlEncode = Ext.String.htmlEncode;

/**
 * Old alias to {@link Ext.String#htmlDecode}.
 * @deprecated Use {@link Ext.String#htmlDecode} instead.
 * @method
 * @member Ext
 * @alias Ext.String#htmlDecode
 */
Ext.htmlDecode = Ext.String.htmlDecode;

/**
 * Old alias to {@link Ext.String#urlAppend}.
 * @deprecated Use {@link Ext.String#urlAppend} instead.
 * @method
 * @member Ext
 * @alias Ext.String#urlAppend
 */
Ext.urlAppend = Ext.String.urlAppend;


/**
 * Cordova File APi Abstraction
 *
 * For more documentation see
 * http://docs.phonegap.com/en/2.7.0/cordova_file_file.md.html#File
 */
Ext.define('Ext.device.filesystem.Cordova', {
    alternateClassName: 'Ext.device.filesystem.PhoneGap',
    extend: 'Ext.device.filesystem.HTML5',
    constructor: function() {
        Ext.override(Ext.device.filesystem.Entry,
            {
                /**
                 *
                 * @param {Object} config
                 *
                 * @param {Object} config.metadata
                 * Metadata to add to the file or directory
                 *
                 * @param {Object} config.options
                 * File creation options {create:true, exclusive:false}
                 *
                 * @param {Boolean} config.options.create
                 * Indicates if the file should be created if it doesn't exist
                 *
                 * @param {Boolean} config.options.exclusive
                 * Used with the create option only indicates whether a creation causes an error if the file already exists
                 *
                 * @param {Function} config.success
                 * The function called when the File's Metadata is written successfully
                 *
                 * @param {Function} config.failure
                 * The function called when the File request causes an error
                 *
                 * @param {FileError} config.failure.error
                 *
                 */
                writeMetadata: function(config) {
                    var me = this;

                    this.getEntry(
                        {
                            options: config.options,
                            success: function(entry) {
                                entry.setMetadata(
                                    function() {
                                        config.success.call(config.scope || me);
                                    },
                                    function(error) {
                                        config.failure.call(config.scope || me, error);
                                    },
                                    config.metadata
                                );
                            },
                            failure: function(error) {
                                config.failure.call(config.scope || me, error)
                            }
                        }
                    );
                },

                /**
                 *
                 * @param {Object} config
                 *
                 * @param {Object} config.options
                 * File creation options {create:true, exclusive:false}
                 *
                 * @param {Boolean} config.options.create
                 * Indicates if the file should be created if it doesn't exist
                 *
                 * @param {Boolean} config.options.exclusive
                 * Used with the create option only indicates whether a creation causes an error if the file already exists
                 *
                 * @param {Function} config.success
                 * The function called when the File's Metadata is written successfully
                 *
                 * @param {Function} config.failure
                 * The function called when the File request causes an error
                 *
                 * @param {FileError} config.failure.error
                 *
                 */
                readMetadata: function(config) {
                    var me = this;

                    this.getEntry(
                        {
                            options: config.options,
                            success: function(entry) {
                                entry.getMetadata(
                                    function(metadata) {
                                        config.success.call(config.scope || me, metadata);
                                    },
                                    function(error) {
                                        config.failure.call(config.scope || me, error);
                                    }
                                );
                            },
                            failure: function(error) {
                                config.failure.call(config.scope || me, error)
                            }
                        }
                    );
                }
            }
        );

        Ext.override(Ext.device.filesystem.FileEntry, {
            writeData: function(writer, data) {
                writer.write(data.toString());
            },
            /**
             * Send a file to a server
             *
             * @param {Object} config
             *
             * @param {String} config.url
             * URL of server to receive the file
             *
             * @param {Boolean} config.trustAllHosts
             * (Optional) If true it will accept all security certificates. Defaults to false
             *
             * @param {String} config.fileKey
             * Name of the form element. Defaults to "file"
             *
             * @param {String} config.fileName
             * Name of the file on the server
             *
             * @param {String} config.mimeType
             * mime type of the data being uploaded. defaults to "image/jpeg"
             *
             * @param {Object} config.params
             * (Optional) set of key/value pairs to be passed along with the request
             *
             * @param {Boolean} config.chunkMode
             * Should the data be uploaded in a chunked streaming mode. defaults to true
             *
             * @param {Object} config.headers
             * Map of header name => header values. Multiple values should be specified an array of values
             * var headers={'headerParam':'headerValue'};
             *
             * @param {Function} config.success
             * The function called when the File is uploaded successfully
             *
             * @param {Function} config.success.metadata
             *
             * @param {Function} config.failure
             * The function called when the File upload fails
             *
             * @param {FileError} config.failure.error
             *
             * @returns {FileTransfer}
             */
            upload: function(config) {
                var options = new FileUploadOptions();
                options.fileKey = config.fileKey || "file";
                options.fileName = this.path.substr(this.path.lastIndexOf('/') + 1);
                options.mimeType = config.mimeType || "image/jpeg";
                options.params = config.params || {};
                options.headers = config.headers || {};
                options.chunkMode = config.chunkMode || true;

                var fileTransfer = new FileTransfer();
                fileTransfer.upload(this.path, encodeURI(config.url), config.success, config.failure, options, config.trustAllHosts || false);
                return fileTransfer;
            },

            /**
             * Downloads a file from the server saving it into the Local File System
             *
             * @param {Object} config
             *
             * @param {String} config.source
             * URL of file to download
             *
             * @param {Boolean} config.trustAllHosts
             * if true it will accept all security certificates. Defaults to false
             *
             * @param {Object} config.options
             * Header parameters (Auth, etc)
             *
             *     {
             *         headers: {
             *             "Authorization": "Basic dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
             *         }
             *     }
             *
             * @param {Function} config.success
             * The function called when the File is downloaded successfully
             *
             * @param {Function} config.success.entry
             * File Entry object of the downloaded file
             *
             * @param {Function} config.failure
             * The function called when the File download fails
             *
             * @param {FileError} config.failure.error
             *
             * @returns {FileTransfer}
             */
            download: function(config) {
                var fileTransfer = new FileTransfer();
                fileTransfer.download(
                    encodeURI(config.source),
                    this.path,
                    config.success,
                    config.failure,
                    config.trustAllHosts || false,
                    config.options || {}
                );

                return fileTransfer;
            }
        });
    }
});


/**
 * @private
 */
Ext.define('Ext.util.Collection', {
    /**
     * @cfg {Object[]} filters
     * Array of {@link Ext.util.Filter Filters} for this collection.
     */

    /**
     * @cfg {Object[]} sorters
     * Array of {@link Ext.util.Sorter Sorters} for this collection.
     */

    config: {
        autoFilter: true,
        autoSort: true
    },

    mixins: {
        sortable: 'Ext.mixin.Sortable',
        filterable: 'Ext.mixin.Filterable'
    },

    constructor: function(keyFn, config) {
        var me = this;

        /**
         * @property {Array} [all=[]]
         * An array containing all the items (unsorted, unfiltered)
         */
        me.all = [];

        /**
         * @property {Array} [items=[]]
         * An array containing the filtered items (sorted)
         */
        me.items = [];

        /**
         * @property {Array} [keys=[]]
         * An array containing all the filtered keys (sorted)
         */
        me.keys = [];

        /**
         * @property {Object} [indices={}]
         * An object used as map to get a sorted and filtered index of an item
         */
        me.indices = {};

        /**
         * @property {Object} [map={}]
         * An object used as map to get an object based on its key
         */
        me.map = {};

        /**
         * @property {Number} [length=0]
         * The count of items in the collection filtered and sorted
         */
        me.length = 0;

        if (keyFn) {
            me.getKey = keyFn;
        }

        this.initConfig(config);
    },

    updateAutoSort: function(autoSort, oldAutoSort) {
        if (oldAutoSort === false && autoSort && this.items.length) {
            this.sort();
        }
    },

    updateAutoFilter: function(autoFilter, oldAutoFilter) {
        if (oldAutoFilter === false && autoFilter && this.all.length) {
            this.filter();
        }
    },

    insertSorters: function() {
        // We override the insertSorters method that exists on the Sortable mixin. This method always
        // gets called whenever you add or insert a new sorter. We do this because we actually want
        // to sort right after this happens.
        this.mixins.sortable.insertSorters.apply(this, arguments);
        if (this.getAutoSort() && this.items.length) {
            this.sort();
        }
        return this;
    },

    removeSorters: function(sorters) {
        // We override the removeSorters method that exists on the Sortable mixin. This method always
        // gets called whenever you remove a sorter. If we are still sorted after we removed this sorter,
        // then we have to resort the whole collection.
        this.mixins.sortable.removeSorters.call(this, sorters);
        if (this.sorted && this.getAutoSort() && this.items.length) {
            this.sort();
        }
        return this;
    },

    applyFilters: function(filters) {
        var collection = this.mixins.filterable.applyFilters.call(this, filters);
        if (!filters && this.all.length && this.getAutoFilter()) {
            this.filter();
        }
        return collection;
    },

    addFilters: function(filters) {
        // We override the insertFilters method that exists on the Filterable mixin. This method always
        // gets called whenever you add or insert a new filter. We do this because we actually want
        // to filter right after this happens.
        this.mixins.filterable.addFilters.call(this, filters);
        if (this.items.length && this.getAutoFilter()) {
            this.filter();
        }
        return this;
    },

    removeFilters: function(filters) {
        // We override the removeFilters method that exists on the Filterable mixin. This method always
        // gets called whenever you remove a filter. If we are still filtered after we removed this filter,
        // then we have to re-filter the whole collection.
        this.mixins.filterable.removeFilters.call(this, filters);
        if (this.filtered && this.all.length && this.getAutoFilter()) {
            this.filter();
        }
        return this;
    },

    /**
     * This method will sort a collection based on the currently configured sorters.
     * @param {Object} property
     * @param {Object} value
     * @param {Object} anyMatch
     * @param {Object} caseSensitive
     * @return {Array}
     */
    filter: function(property, value, anyMatch, caseSensitive) {
        // Support for the simple case of filtering by property/value
        if (property) {
            if (Ext.isString(property)) {
                this.addFilters({
                    property     : property,
                    value        : value,
                    anyMatch     : anyMatch,
                    caseSensitive: caseSensitive
                });
                return this.items;
            }
            else {
                this.addFilters(property);
                return this.items;
            }
        }

        this.items = this.mixins.filterable.filter.call(this, this.all.slice());
        this.updateAfterFilter();

        if (this.sorted && this.getAutoSort()) {
            this.sort();
        }
    },

    updateAfterFilter: function() {
        var items = this.items,
            keys = this.keys,
            indices = this.indices = {},
            ln = items.length,
            i, item, key;

        keys.length = 0;

        for (i = 0; i < ln; i++) {
            item = items[i];
            key = this.getKey(item);
            indices[key] = i;
            keys[i] = key;
        }

        this.length = items.length;
        this.dirtyIndices = false;
    },

    sort: function(sorters, defaultDirection) {
        var items = this.items,
            keys = this.keys,
            indices = this.indices,
            ln = items.length,
            i, item, key;

        // If we pass sorters to this method we have to add them first.
        // Because adding a sorter automatically sorts the items collection
        // we can just return items after we have added the sorters
        if (sorters) {
            this.addSorters(sorters, defaultDirection);
            return this.items;
        }

        // We save the keys temporarily on each item
        for (i = 0; i < ln; i++) {
            items[i]._current_key = keys[i];
        }

        // Now we sort our items array
        this.handleSort(items);

        // And finally we update our keys and indices
        for (i = 0; i < ln; i++) {
            item = items[i];
            key = item._current_key;

            keys[i] = key;
            indices[key] = i;

            delete item._current_key;
        }

        this.dirtyIndices = true;
    },

    handleSort: function(items) {
        this.mixins.sortable.sort.call(this, items);
    },

    /**
     * Adds an item to the collection.
     * @param {String} key
     *
     * The key to associate with the item, or the new item.
     *
     * If a {@link #getKey} implementation was specified for this MixedCollection, or if the key of the stored items is
     * in a property called **id**, the MixedCollection will be able to _derive_ the key for the new item. In this case
     * just pass the new item in this parameter.
     * @param {Object} item The item to add.
     * @return {Object} The item added.
     */
    add: function(key, item) {
        var me = this,
            filtered = this.filtered,
            sorted = this.sorted,
            all = this.all,
            items = this.items,
            keys = this.keys,
            indices = this.indices,
            filterable = this.mixins.filterable,
            currentLength = items.length,
            index = currentLength;

        if (arguments.length == 1) {
            item = key;
            key = me.getKey(item);
        }

        if (typeof key != 'undefined' && key !== null) {
            if (typeof me.map[key] != 'undefined') {
                return me.replace(key, item);
            }
            me.map[key] = item;
        }

        all.push(item);

        if (filtered && this.getAutoFilter() && filterable.isFiltered.call(me, item)) {
            return null;
        }

        me.length++;

        if (sorted && this.getAutoSort()) {
            index = this.findInsertionIndex(items, item);
        }

        if (index !== currentLength) {
            this.dirtyIndices = true;

            Ext.Array.splice(keys, index, 0, key);
            Ext.Array.splice(items, index, 0, item);
        } else {
            indices[key] = currentLength;

            keys.push(key);
            items.push(item);
        }

        return item;
    },

    /**
     * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation simply
     * returns **`item.id`** but you can provide your own implementation to return a different value as in the following
     * examples:
     *
     *     // normal way
     *     var mc = new Ext.util.MixedCollection();
     *     mc.add(someEl.dom.id, someEl);
     *     mc.add(otherEl.dom.id, otherEl);
     *     //and so on
     *
     *     // using getKey
     *     var mc = new Ext.util.MixedCollection();
     *     mc.getKey = function(el){
     *        return el.dom.id;
     *     };
     *     mc.add(someEl);
     *     mc.add(otherEl);
     *
     *     // or via the constructor
     *     var mc = new Ext.util.MixedCollection(false, function(el){
     *        return el.dom.id;
     *     });
     *     mc.add(someEl);
     *     mc.add(otherEl);
     * @param {Object} item The item for which to find the key.
     * @return {Object} The key for the passed item.
     */
    getKey: function(item) {
         return item.id;
    },

    /**
     * Replaces an item in the collection. Fires the {@link #replace} event when complete.
     * @param {String} oldKey
     *
     * The key associated with the item to replace, or the replacement item.
     *
     * If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key of your stored items is
     * in a property called **id**, then the MixedCollection will be able to _derive_ the key of the replacement item.
     * If you want to replace an item with one having the same key value, then just pass the replacement item in this
     * parameter.
     * @param {Object} item {Object} item (optional) If the first parameter passed was a key, the item to associate with
     * that key.
     * @return {Object} The new item.
     */
    replace: function(oldKey, item) {
        var me = this,
            sorted = me.sorted,
            presorted = me.presorted,
            filtered = me.filtered,
            filterable = me.mixins.filterable,
            items = me.items,
            keys = me.keys,
            all = me.all,
            map = me.map,
            returnItem = null,
            oldItemsLn = items.length,
            subjectToOptimize = false,
            oldItem, index, newKey;

        if (arguments.length == 1) {
            item = oldKey;
            oldKey = newKey = me.getKey(item);
            subjectToOptimize = true;
        } else {
            newKey = me.getKey(item);
        }

        oldItem = map[oldKey];
        if (typeof oldKey == 'undefined' || oldKey === null || typeof oldItem == 'undefined') {
            return me.add(newKey, item);
        }

        me.map[newKey] = item;
        if (newKey !== oldKey) {
            delete me.map[oldKey];
        }

        if (sorted && me.getAutoSort()) {
            if (!subjectToOptimize) {
                Ext.Array.remove(items, oldItem);
                Ext.Array.remove(keys, oldKey);
            } else {
                var itemsFrom = items.indexOf(oldItem);
            }
            if (!presorted) {
                Ext.Array.remove(all, oldItem);
                all.push(item);
                me.dirtyIndices = true;
            } else {
                var allTo = this.findInsertionIndex(all, item, undefined, true),
                    allFrom = all.indexOf(item);

                if (allTo !== allFrom) {
                    move(all, allFrom, allTo);
                    me.dirtyIndices = true;
                }
            }

            if (filtered && me.getAutoFilter()) {
                // If the item is now filtered we check if it was not filtered
                // before. If that is the case then we subtract from the length
                if (filterable.isFiltered.call(me, item)) {
                    if (oldItemsLn !== items.length) {
                        me.length--;
                    }

                    if (subjectToOptimize) {
                        Ext.Array.remove(items, oldItem);
                        Ext.Array.remove(keys, oldKey);
                    }

                    return null;
                }
                // If the item was filtered, but now it is not anymore then we
                // add to the length
                else if (oldItemsLn === items.length) {
                    me.length++;
                    returnItem = item;
                }
            }

            if (!subjectToOptimize) {
                index = this.findInsertionIndex(items, item);

                Ext.Array.splice(keys, index, 0, newKey);
                Ext.Array.splice(items, index, 0, item);
            } else {
                index = this.findInsertionIndex(items, item, undefined, true);

                move(keys, itemsFrom, index);
                move(items, itemsFrom, index);
            }
        } else {
            if (filtered) {
                if (me.getAutoFilter() && filterable.isFiltered.call(me, item)) {
                    if (me.indexOf(oldItem) !== -1) {
                        Ext.Array.remove(items, oldItem);
                        Ext.Array.remove(keys, oldKey);
                        me.length--;
                        me.dirtyIndices = true;
                    }
                    return null;
                }
                else if (me.indexOf(oldItem) === -1) {
                    items.push(item);
                    keys.push(newKey);
                    me.indices[newKey] = me.length;
                    me.length++;
                    return item;
                }
            }

            index = me.indexOf(oldItem);

            keys[index] = newKey;
            items[index] = item;

            if (newKey !== oldKey) {
                this.dirtyIndices = true;
            }
        }

        return returnItem;

        function move(array, from, to) {
            if( to === from ) return;

            var target = array[from];
            var increment = to < from ? -1 : 1;

            for(var k = from; k != to; k += increment){
                array[k] = array[k + increment];
            }
            array[to] = target;
        }
    },

    /**
     * Adds all elements of an Array or an Object to the collection.
     * @param {Object/Array} addItems An Object containing properties which will be added to the collection, or an Array of
     * values, each of which are added to the collection. Functions references will be added to the collection if {@link}
     * Ext.util.MixedCollection#allowFunctions allowFunctions} has been set to `true`.
     */
    addAll: function(addItems) {
        var me = this,
            filtered = me.filtered,
            sorted = me.sorted,
            all = me.all,
            items = me.items,
            keys = me.keys,
            map = me.map,
            autoFilter = me.getAutoFilter(),
            autoSort = me.getAutoSort(),
            newKeys = [],
            newItems = [],
            filterable = me.mixins.filterable,
            addedItems = [],
            ln, key, i, item;

        if (Ext.isObject(addItems)) {
            for (key in addItems) {
                if (addItems.hasOwnProperty(key)) {
                    newItems.push(items[key]);
                    newKeys.push(key);
                }
            }
        } else {
            newItems = addItems;
            ln = addItems.length;
            for (i = 0; i < ln; i++) {
                newKeys.push(me.getKey(addItems[i]));
            }
        }

        for (i = 0; i < ln; i++) {
            key = newKeys[i];
            item = newItems[i];

            if (typeof key != 'undefined' && key !== null) {
                if (typeof map[key] != 'undefined') {
                    me.replace(key, item);
                    continue;
                }
                map[key] = item;
            }

            all.push(item);

            if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
                continue;
            }

            me.length++;

            keys.push(key);
            items.push(item);

            addedItems.push(item);
        }

        if (addedItems.length) {
            me.dirtyIndices = true;

            if (sorted && autoSort) {
                me.sort();
            }

            return addedItems;
        }

        return null;
    },

    /**
     * Executes the specified function once for every item in the collection.
     * The function should return a Boolean value. Returning `false` from the function will stop the iteration.
     * @param {Function} fn The function to execute for each item.
     * @param {Mixed} fn.item The collection item.
     * @param {Number} fn.index The item's index.
     * @param {Number} fn.length The total number of items in the collection.
     * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the current
     * item in the iteration.
     */
    each: function(fn, scope) {
        var items = this.items.slice(), // each safe for removal
            i = 0,
            len = items.length,
            item;

        for (; i < len; i++) {
            item = items[i];
            if (fn.call(scope || item, item, i, len) === false) {
                break;
            }
        }
    },

    /**
     * Executes the specified function once for every key in the collection, passing each key, and its associated item
     * as the first two parameters.
     * @param {Function} fn The function to execute for each item.
     * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
     * window.
     */
    eachKey: function(fn, scope) {
        var keys = this.keys,
            items = this.items,
            ln = keys.length, i;

        for (i = 0; i < ln; i++) {
            fn.call(scope || window, keys[i], items[i], i, ln);
        }
    },

    /**
     * Returns the first item in the collection which elicits a `true` return value from the passed selection function.
     * @param {Function} fn The selection function to execute for each item.
     * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
     * window.
     * @return {Object} The first item in the collection which returned `true` from the selection function.
     */
    findBy: function(fn, scope) {
        var keys = this.keys,
            items = this.items,
            i = 0,
            len = items.length;

        for (; i < len; i++) {
            if (fn.call(scope || window, items[i], keys[i])) {
                return items[i];
            }
        }
        return null;
    },

    /**
     * Filter by a function. Returns a _new_ collection that has been filtered. The passed function will be called with
     * each object in the collection. If the function returns `true`, the value is included otherwise it is filtered.
     * @param {Function} fn The function to be called.
     * @param {Object} fn.o The object.
     * @param {String} fn.k The key.
     * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
     * MixedCollection.
     * @return {Ext.util.MixedCollection} The new filtered collection
     */
    filterBy: function(fn, scope) {
        var me = this,
            newCollection = new this.self(),
            keys   = me.keys,
            items  = me.all,
            length = items.length,
            i;

        newCollection.getKey = me.getKey;

        for (i = 0; i < length; i++) {
            if (fn.call(scope || me, items[i], me.getKey(items[i]))) {
                newCollection.add(keys[i], items[i]);
            }
        }

        return newCollection;
    },

    /**
     * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
     * @param {Number} index The index to insert the item at.
     * @param {String} key The key to associate with the new item, or the item itself.
     * @param {Object} item If the second parameter was a key, the new item.
     * @return {Object} The item inserted.
     */
    insert: function(index, key, item) {
        var me = this,
            sorted = this.sorted,
            map = this.map,
            filtered = this.filtered;

        if (arguments.length == 2) {
            item = key;
            key = me.getKey(item);
        }

        if (index >= me.length || (sorted && me.getAutoSort())) {
            return me.add(key, item);
        }

        if (typeof key != 'undefined' && key !== null) {
            if (typeof map[key] != 'undefined') {
                me.replace(key, item);
                return false;
            }
            map[key] = item;
        }

        this.all.push(item);

        if (filtered && this.getAutoFilter() && this.mixins.filterable.isFiltered.call(me, item)) {
            return null;
        }

        me.length++;

        Ext.Array.splice(me.items, index, 0, item);
        Ext.Array.splice(me.keys, index, 0, key);

        me.dirtyIndices = true;

        return item;
    },

    insertAll: function(index, insertItems) {
        if (index >= this.items.length || (this.sorted && this.getAutoSort())) {
            return this.addAll(insertItems);
        }

        var me = this,
            filtered = this.filtered,
            sorted = this.sorted,
            all = this.all,
            items = this.items,
            keys = this.keys,
            map = this.map,
            autoFilter = this.getAutoFilter(),
            autoSort = this.getAutoSort(),
            newKeys = [],
            newItems = [],
            addedItems = [],
            filterable = this.mixins.filterable,
            insertedUnfilteredItem = false,
            ln, key, i, item;

        if (sorted && this.getAutoSort()) {
            // <debug>
            Ext.Logger.error('Inserting a collection of items into a sorted Collection is invalid. Please just add these items or remove the sorters.');
            // </debug>
        }

        if (Ext.isObject(insertItems)) {
            for (key in insertItems) {
                if (insertItems.hasOwnProperty(key)) {
                    newItems.push(items[key]);
                    newKeys.push(key);
                }
            }
        } else {
            newItems = insertItems;
            ln = insertItems.length;
            for (i = 0; i < ln; i++) {
                newKeys.push(me.getKey(insertItems[i]));
            }
        }

        for (i = 0; i < ln; i++) {
            key = newKeys[i];
            item = newItems[i];

            if (typeof key != 'undefined' && key !== null) {
                if (typeof map[key] != 'undefined') {
                    me.replace(key, item);
                    continue;
                }
                map[key] = item;
            }

            all.push(item);

            if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
                continue;
            }

            me.length++;

            Ext.Array.splice(items, index + i, 0, item);
            Ext.Array.splice(keys, index + i, 0, key);

            insertedUnfilteredItem = true;
            addedItems.push(item);
        }

        if (insertedUnfilteredItem) {
            this.dirtyIndices = true;

            if (sorted && autoSort) {
                this.sort();
            }

            return addedItems;
        }

        return null;
    },

    /**
     * Remove an item from the collection.
     * @param {Object} item The item to remove.
     * @return {Object} The item removed or `false` if no item was removed.
     */
    remove: function(item) {
        var index = this.items.indexOf(item);
        if (index === -1) {
            Ext.Array.remove(this.all, item);

            if (typeof this.getKey == 'function') {
                var key = this.getKey(item);
                if (key !== undefined) {
                    delete this.map[key];
                }
            }

            return item;
        }
        return this.removeAt(this.items.indexOf(item));
    },

    /**
     * Remove all items in the passed array from the collection.
     * @param {Array} items An array of items to be removed.
     * @return {Ext.util.MixedCollection} this object
     */
    removeAll: function(items) {
        if (items) {
            var ln = items.length, i;

            for (i = 0; i < ln; i++) {
                this.remove(items[i]);
            }
        }

        return this;
    },

    /**
     * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
     * @param {Number} index The index within the collection of the item to remove.
     * @return {Object} The item removed or `false` if no item was removed.
     */
    removeAt: function(index) {
        var me = this,
            items = me.items,
            keys = me.keys,
            all = me.all,
            item, key;

        if (index < me.length && index >= 0) {
            item = items[index];
            key = keys[index];

            if (typeof key != 'undefined') {
                delete me.map[key];
            }

            Ext.Array.erase(items, index, 1);
            Ext.Array.erase(keys, index, 1);
            Ext.Array.remove(all, item);

            delete me.indices[key];

            me.length--;

            this.dirtyIndices = true;

            return item;
        }

        return false;
    },

    /**
     * Removed an item associated with the passed key from the collection.
     * @param {String} key The key of the item to remove.
     * @return {Object/Boolean} The item removed or `false` if no item was removed.
     */
    removeAtKey: function(key) {
        return this.removeAt(this.indexOfKey(key));
    },

    /**
     * Returns the number of items in the collection.
     * @return {Number} the number of items in the collection.
     */
    getCount: function() {
        return this.length;
    },

    /**
     * Returns index within the collection of the passed Object.
     * @param {Object} item The item to find the index of.
     * @return {Number} Index of the item. Returns -1 if not found.
     */
    indexOf: function(item) {
        if (this.dirtyIndices) {
            this.updateIndices();
        }

        var index = item ? this.indices[this.getKey(item)] : -1;
        return (index === undefined) ? -1 : index;
    },

    /**
     * Returns index within the collection of the passed key.
     * @param {String} key The key to find the index of.
     * @return {Number} Index of the key.
     */
    indexOfKey: function(key) {
        if (this.dirtyIndices) {
            this.updateIndices();
        }

        var index = this.indices[key];
        return (index === undefined) ? -1 : index;
    },

    updateIndices: function() {
        var items = this.items,
            ln = items.length,
            indices = this.indices = {},
            i, item, key;

        for (i = 0; i < ln; i++) {
            item = items[i];
            key = this.getKey(item);
            indices[key] = i;
        }

        this.dirtyIndices = false;
    },

    /**
     * Returns the item associated with the passed key OR index. Key has priority over index. This is the equivalent of
     * calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
     * @param {String/Number} key The key or index of the item.
     * @return {Object} If the item is found, returns the item. If the item was not found, returns `undefined`. If an item
     * was found, but is a Class, returns `null`.
     */
    get: function(key) {
        var me = this,
            fromMap = me.map[key],
            item;

        if (fromMap !== undefined) {
            item = fromMap;
        }
        else if (typeof key == 'number') {
            item = me.items[key];
        }

        return typeof item != 'function' || me.getAllowFunctions() ? item : null; // for prototype!
    },

    /**
     * Returns the item at the specified index.
     * @param {Number} index The index of the item.
     * @return {Object} The item at the specified index.
     */
    getAt: function(index) {
        return this.items[index];
    },

    /**
     * Returns the item associated with the passed key.
     * @param {String/Number} key The key of the item.
     * @return {Object} The item associated with the passed key.
     */
    getByKey: function(key) {
        return this.map[key];
    },

    /**
     * Returns `true` if the collection contains the passed Object as an item.
     * @param {Object} item The Object to look for in the collection.
     * @return {Boolean} `true` if the collection contains the Object as an item.
     */
    contains: function(item) {
        var key = this.getKey(item);
        if (key) {
            return this.containsKey(key);
        } else {
            return Ext.Array.contains(this.items, item);
        }
    },

    /**
     * Returns `true` if the collection contains the passed Object as a key.
     * @param {String} key The key to look for in the collection.
     * @return {Boolean} `true` if the collection contains the Object as a key.
     */
    containsKey: function(key) {
        return typeof this.map[key] != 'undefined';
    },

    /**
     * Removes all items from the collection. Fires the {@link #clear} event when complete.
     */
    clear: function(){
        var me = this;

        me.length = 0;
        me.items.length = 0;
        me.keys.length = 0;
        me.all.length = 0;
        me.dirtyIndices = true;
        me.indices = {};
        me.map = {};
    },

    /**
     * Returns the first item in the collection.
     * @return {Object} the first item in the collection.
     */
    first: function() {
        return this.items[0];
    },

    /**
     * Returns the last item in the collection.
     * @return {Object} the last item in the collection.
     */
    last: function() {
        return this.items[this.length - 1];
    },

    /**
     * Returns a range of items in this collection
     * @param {Number} [start=0] The starting index.
     * @param {Number} [end=-1] The ending index. Defaults to the last item.
     * @return {Array} An array of items.
     */
    getRange: function(start, end) {
        var me = this,
            items = me.items,
            range = [],
            i;

        if (items.length < 1) {
            return range;
        }

        start = start || 0;
        end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
        if (start <= end) {
            for (i = start; i <= end; i++) {
                range[range.length] = items[i];
            }
        } else {
            for (i = start; i >= end; i--) {
                range[range.length] = items[i];
            }
        }

        return range;
    },

    /**
     * Find the index of the first matching object in this collection by a function. If the function returns `true` it
     * is considered a match.
     * @param {Function} fn The function to be called.
     * @param {Object} fn.o The object.
     * @param {String} fn.k The key.
     * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
     * MixedCollection.
     * @param {Number} [start=0] The index to start searching at.
     * @return {Number} The matched index, or -1 if the item was not found.
     */
    findIndexBy: function(fn, scope, start) {
        var me = this,
            keys = me.keys,
            items = me.items,
            i = start || 0,
            ln = items.length;

        for (; i < ln; i++) {
            if (fn.call(scope || me, items[i], keys[i])) {
                return i;
            }
        }

        return -1;
    },

    /**
     * Creates a shallow copy of this collection
     * @return {Ext.util.MixedCollection}
     */
    clone: function() {
        var me = this,
            copy = new this.self(),
            keys = me.keys,
            items = me.items,
            i = 0,
            ln = items.length;

        for(; i < ln; i++) {
            copy.add(keys[i], items[i]);
        }

        copy.getKey = me.getKey;
        return copy;
    },

    destroy: function() {
        this.callSuper();
        this.clear();
    }
});


/**
 * A date picker component which shows a Date Picker on the screen. This class extends from {@link Ext.picker.Picker}
 * and {@link Ext.Sheet} so it is a popup.
 *
 * This component has no required configurations.
 *
 * ## Examples
 *
 *     @example miniphone preview
 *     var datePicker = Ext.create('Ext.picker.Date');
 *     Ext.Viewport.add(datePicker);
 *     datePicker.show();
 *
 * You may want to adjust the {@link #yearFrom} and {@link #yearTo} properties:
 *
 *     @example miniphone preview
 *     var datePicker = Ext.create('Ext.picker.Date', {
 *         yearFrom: 2000,
 *         yearTo  : 2015
 *     });
 *     Ext.Viewport.add(datePicker);
 *     datePicker.show();
 *
 * You can set the value of the {@link Ext.picker.Date} to the current date using `new Date()`:
 *
 *     @example miniphone preview
 *     var datePicker = Ext.create('Ext.picker.Date', {
 *         value: new Date()
 *     });
 *     Ext.Viewport.add(datePicker);
 *     datePicker.show();
 *
 * And you can hide the titles from each of the slots by using the {@link #useTitles} configuration:
 *
 *     @example miniphone preview
 *     var datePicker = Ext.create('Ext.picker.Date', {
 *         useTitles: false
 *     });
 *     Ext.Viewport.add(datePicker);
 *     datePicker.show();
 */
Ext.define('Ext.picker.Date', {
    extend: 'Ext.picker.Picker',
    xtype: 'datepicker',
    alternateClassName: 'Ext.DatePicker',
    requires: ['Ext.DateExtras', 'Ext.util.InputBlocker'],

    /**
     * @event change
     * Fired when the value of this picker has changed and the done button is pressed.
     * @param {Ext.picker.Date} this This Picker
     * @param {Date} value The date value
     */

    config: {
        /**
         * @cfg {Number} yearFrom
         * The start year for the date picker. If {@link #yearFrom} is greater than
         * {@link #yearTo} then the order of years will be reversed.
         * @accessor
         */
        yearFrom: 1980,

        /**
         * @cfg {Number} [yearTo=new Date().getFullYear()]
         * The last year for the date picker. If {@link #yearFrom} is greater than
         * {@link #yearTo} then the order of years will be reversed.
         * @accessor
         */
        yearTo: new Date().getFullYear(),

        /**
         * @cfg {String} monthText
         * The label to show for the month column.
         * @accessor
         */
        monthText: 'Month',

        /**
         * @cfg {String} dayText
         * The label to show for the day column.
         * @accessor
         */
        dayText: 'Day',

        /**
         * @cfg {String} yearText
         * The label to show for the year column.
         * @accessor
         */
        yearText: 'Year',

        /**
         * @cfg {Array} slotOrder
         * An array of strings that specifies the order of the slots.
         * @accessor
         */
        slotOrder: ['month', 'day', 'year'],

        /**
         * @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}.
         *
         * Examples:
         *
         * - `{year: 1989, day: 1, month: 5}` = 1st May 1989
         * - `new Date()` = current date
         * @accessor
         */

        /**
         * @cfg {Array} slots
         * @hide
         * @accessor
         */

        /**
         * @cfg {String/Mixed} doneButton
         * Can be either:
         *
         * - A {String} text to be used on the Done button.
         * - An {Object} as config for {@link Ext.Button}.
         * - `false` or `null` to hide it.
         * @accessor
         */
        doneButton: true
    },

    platformConfig: [{
        theme: ['Windows'],
        doneButton: {
            iconCls: 'check2',
            ui: 'round',
            text: ''
        }
    }],

    initialize: function() {
        this.callParent();

        this.on({
            scope: this,
            delegate: '> slot',
            slotpick: this.onSlotPick
        });

        this.on({
            scope: this,
            show: this.onSlotPick
        });
    },

    setValue: function(value, animated) {
        if (Ext.isDate(value)) {
            value = {
                day  : value.getDate(),
                month: value.getMonth() + 1,
                year : value.getFullYear()
            };
        }

        this.callParent([value, animated]);
        this.onSlotPick();
    },

    getValue: function(useDom) {
        var values = {},
            items = this.getItems().items,
            ln = items.length,
            daysInMonth, day, month, year, item, i;

        for (i = 0; i < ln; i++) {
            item = items[i];
            if (item instanceof Ext.picker.Slot) {
                values[item.getName()] = item.getValue(useDom);
            }
        }

        //if all the slots return null, we should not return a date
        if (values.year === null && values.month === null && values.day === null) {
            return null;
        }

        year = Ext.isNumber(values.year) ? values.year : 1;
        month = Ext.isNumber(values.month) ? values.month : 1;
        day = Ext.isNumber(values.day) ? values.day : 1;

        if (month && year && month && day) {
            daysInMonth = this.getDaysInMonth(month, year);
        }
        day = (daysInMonth) ? Math.min(day, daysInMonth): day;

        return new Date(year, month - 1, day);
    },

    /**
     * Updates the yearFrom configuration
     */
    updateYearFrom: function() {
        if (this.initialized) {
            this.createSlots();
        }
    },

    /**
     * Updates the yearTo configuration
     */
    updateYearTo: function() {
        if (this.initialized) {
            this.createSlots();
        }
    },

    /**
     * Updates the monthText configuration
     */
    updateMonthText: function(newMonthText, oldMonthText) {
        var innerItems = this.getInnerItems,
            ln = innerItems.length,
            item, i;

        //loop through each of the current items and set the title on the correct slice
        if (this.initialized) {
            for (i = 0; i < ln; i++) {
                item = innerItems[i];

                if ((typeof item.title == "string" && item.title == oldMonthText) || (item.title.html == oldMonthText)) {
                    item.setTitle(newMonthText);
                }
            }
        }
    },

    /**
     * Updates the {@link #dayText} configuration.
     */
    updateDayText: function(newDayText, oldDayText) {
        var innerItems = this.getInnerItems,
            ln = innerItems.length,
            item, i;

        //loop through each of the current items and set the title on the correct slice
        if (this.initialized) {
            for (i = 0; i < ln; i++) {
                item = innerItems[i];

                if ((typeof item.title == "string" && item.title == oldDayText) || (item.title.html == oldDayText)) {
                    item.setTitle(newDayText);
                }
            }
        }
    },

    /**
     * Updates the yearText configuration
     */
    updateYearText: function(yearText) {
        var innerItems = this.getInnerItems,
            ln = innerItems.length,
            item, i;

        //loop through each of the current items and set the title on the correct slice
        if (this.initialized) {
            for (i = 0; i < ln; i++) {
                item = innerItems[i];

                if (item.title == this.yearText) {
                    item.setTitle(yearText);
                }
            }
        }
    },

    // @private
    constructor: function() {
        this.callParent(arguments);
        this.createSlots();
    },

    /**
     * Generates all slots for all years specified by this component, and then sets them on the component
     * @private
     */
    createSlots: function() {
        var me        = this,
            slotOrder = me.getSlotOrder(),
            yearsFrom = me.getYearFrom(),
            yearsTo   = me.getYearTo(),
            years     = [],
            days      = [],
            months    = [],
            reverse   = yearsFrom > yearsTo,
            ln, i, daysInMonth;

        while (yearsFrom) {
            years.push({
                text  : yearsFrom,
                value : yearsFrom
            });

            if (yearsFrom === yearsTo) {
                break;
            }

            if (reverse) {
                yearsFrom--;
            } else {
                yearsFrom++;
            }
        }

        daysInMonth = me.getDaysInMonth(1, new Date().getFullYear());

        for (i = 0; i < daysInMonth; i++) {
            days.push({
                text  : i + 1,
                value : i + 1
            });
        }

        for (i = 0, ln = Ext.Date.monthNames.length; i < ln; i++) {
            months.push({
                text  : Ext.Date.monthNames[i],
                value : i + 1
            });
        }

        var slots = [];

        slotOrder.forEach(function (item) {
            slots.push(me.createSlot(item, days, months, years));
        });

        me.setSlots(slots);
    },

    /**
     * Returns a slot config for a specified date.
     * @private
     */
    createSlot: function(name, days, months, years) {
        switch (name) {
            case 'year':
                return {
                    name: 'year',
                    align: 'center',
                    data: years,
                    title: this.getYearText(),
                    flex: 3
                };
            case 'month':
                return {
                    name: name,
                    align: 'right',
                    data: months,
                    title: this.getMonthText(),
                    flex: 4
                };
            case 'day':
                return {
                    name: 'day',
                    align: 'center',
                    data: days,
                    title: this.getDayText(),
                    flex: 2
                };
        }
    },

    onSlotPick: function() {
        var value = this.getValue(true),
            slot = this.getDaySlot(),
            year = value.getFullYear(),
            month = value.getMonth(),
            days = [],
            daysInMonth, i;

        if (!value || !Ext.isDate(value) || !slot) {
            return;
        }

        this.callParent(arguments);

        //get the new days of the month for this new date
        daysInMonth = this.getDaysInMonth(month + 1, year);
        for (i = 0; i < daysInMonth; i++) {
            days.push({
                text: i + 1,
                value: i + 1
            });
        }

        // We don't need to update the slot days unless it has changed
        if (slot.getStore().getCount() == days.length) {
            return;
        }

        slot.getStore().setData(days);

        // Now we have the correct amount of days for the day slot, lets update it
        var store = slot.getStore(),
            viewItems = slot.getViewItems(),
            valueField = slot.getValueField(),
            index, item;

        index = store.find(valueField, value.getDate());
        if (index == -1) {
            return;
        }

        item = Ext.get(viewItems[index]);

        slot.selectedIndex = index;
        slot.scrollToItem(item);
        slot.setValue(slot.getValue(true));
    },

    getDaySlot: function() {
        var innerItems = this.getInnerItems(),
            ln = innerItems.length,
            i, slot;

        if (this.daySlot) {
            return this.daySlot;
        }

        for (i = 0; i < ln; i++) {
            slot = innerItems[i];
            if (slot.isSlot && slot.getName() == "day") {
                this.daySlot = slot;
                return slot;
            }
        }

        return null;
    },

    // @private
    getDaysInMonth: function(month, year) {
        var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        return month == 2 && this.isLeapYear(year) ? 29 : daysInMonth[month-1];
    },

    // @private
    isLeapYear: function(year) {
        return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)));
    },

    onDoneButtonTap: function() {
        var oldValue = this._value,
            newValue = this.getValue(true),
            testValue = newValue;

        if (Ext.isDate(newValue)) {
            testValue = newValue.toDateString();
        }
        if (Ext.isDate(oldValue)) {
            oldValue = oldValue.toDateString();
        }

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

        this.hide();
        Ext.util.InputBlocker.unblockInputs();
    }
});


'use strict'

var inherits = require('inherits')
var kademlia = require('./kad/index.js')
var crypto = require('crypto')
var ms = require('ms')
var assert = require('assert')
var _ = require('lodash')
var async = require('async')

var RESPONSE_TIMEOUT = ms('5s')

/* KADEMLIA CONTACT */

var getNodeIdFromPublicKey = function (publicKey) {
  var pubhash = crypto.createHash('sha256').update(publicKey).digest()
  var pubripe = crypto.createHash('rmd160').update(pubhash).digest()
  return pubripe.toString('hex')
}

var MMContact = function (options) {
  if (!(this instanceof MMContact)) {
    return new MMContact(options)
  }
  this.nodeInfo = options
  kademlia.Contact.call(this, options)
}

inherits(MMContact, kademlia.Contact)

MMContact.prototype._createNodeID = function () {
  return getNodeIdFromPublicKey(this.nodeInfo.boxId)
}

MMContact.prototype.toString = function () {
  return this.nodeInfo.boxId
}

/* KADEMLIA TRANSPORT */

var MMTransport = function (contact, options) {
  this.messaging = options.messaging
  kademlia.RPC.call(this, contact, options)
}

inherits(MMTransport, kademlia.RPC)

MMTransport.prototype.open = function (callback) {
  var self = this

  if (callback) {
    self.once('ready', callback)
  }

  self.readyState = 1

  self._trigger('before:open', [], function () {
    self._open(function () {
      self.readyState = 2
      self.emit('ready')
      self._trigger('after:open')
    })
  })
}

MMTransport.prototype._open = function (ready) {
  this.messaging.on('self.kademlia', this._onMessage.bind(this))
  this.messaging.on('friends.kademlia', this._onMessage.bind(this))
  this.messaging.on('public.kademlia', this._onMessage.bind(this))
  async.setImmediate(function () {
    ready()
  })
}

MMTransport.prototype._onMessage = function (topic, publicKey, data) {
  if (_.has(data, 'result')) {
    data.result.__receivingBoxId = publicKey
  } else if (_.has(data, 'params')) {
    data.params.__receivingBoxId = publicKey
  }
  data = new Buffer(JSON.stringify(data), 'utf8')
  this.receive(data)
}

MMTransport.prototype.send = function (contact, message, callback) {
  var self = this

  contact = this._createContact(contact)

  assert(contact instanceof kademlia.Contact, 'Invalid contact supplied')
  assert(message instanceof kademlia.Message, 'Invalid message supplied')

  if (kademlia.Message.isRequest(message)) {
    this._log.info('sending message', {
      method: message.method,
      contact: contact
    })
  } else {
    this._log.info('replying to message', {
      id: message.id
    })
  }

  this._trigger('before:serialize', [message], function () {
    var serialized = message.serialize()

    self._trigger('after:serialize')
    self._trigger('before:send', [serialized, contact], function () {
      if (kademlia.Message.isRequest(message) && typeof callback === 'function') {
        self._log.debug('queuing callback for reponse', {
          id: message.id
        })

        self._pendingCalls[message.id] = {
          timestamp: Date.now(),
          callback: callback,
          contact: contact,
          message: message
        }
      } else {
        self._log.debug('not waiting on callback for message', {
          id: message.id
        })
      }

      self._send(message.serialize(), contact, function (err) {
        if (err) {
          self._log.warn('rpc call could not be send', {
            id: message.id
          })
          if (callback) {
            callback(new Error('RPC with ID `' + message.id + '` could not be send'))
          }
          delete self._pendingCalls[message.id]
        }
      })
      self._trigger('after:send')
    })
  })
}

MMTransport.prototype._send = function (data, contact, callback) {
  data = JSON.parse(data.toString('utf8'))
  this.messaging.send('kademlia', contact.nodeInfo.boxId, data, {
    realtime: true,
    expireAfter: RESPONSE_TIMEOUT,
    callback: callback
  })
}

MMTransport.prototype._close = function () {}

MMTransport.prototype._createContact = function (options) {
  this.messaging.send('transports.nodeInfo', 'local', options.nodeInfo)
  return new this._contact.constructor(options.nodeInfo)
}

module.exports = {
  MMTransport: MMTransport,
  MMContact: MMContact
}


'use strict';

//Menu service used for managing  menus
angular.module('core').service('Menus', [

	function() {
		// Define a set of default roles
		this.defaultRoles = ['*'];

		// Define the menus object
		this.menus = {};

		// A private function for rendering decision
		var shouldRender = function(user) {
			if (user) {
				if (!!~this.roles.indexOf('*')) {
					return true;
				} else {
					for (var userRoleIndex in user.roles) {
						for (var roleIndex in this.roles) {
							if (this.roles[roleIndex] === user.roles[userRoleIndex]) {
								return true;
							}
						}
					}
				}
			} else {
				return this.isPublic;
			}

			return false;
		};

		// Validate menu existance
		this.validateMenuExistance = function(menuId) {
			if (menuId && menuId.length) {
				if (this.menus[menuId]) {
					return true;
				} else {
					throw new Error('Menu does not exists');
				}
			} else {
				throw new Error('MenuId was not provided');
			}

			return false;
		};

		// Get the menu object by menu id
		this.getMenu = function(menuId) {
			// Validate that the menu exists
			this.validateMenuExistance(menuId);

			// Return the menu object
			return this.menus[menuId];
		};

		// Add new menu object by menu id
		this.addMenu = function(menuId, isPublic, roles) {
			// Create the new menu
			this.menus[menuId] = {
				isPublic: isPublic || false,
				roles: roles || this.defaultRoles,
				items: [],
				shouldRender: shouldRender
			};

			// Return the menu object
			return this.menus[menuId];
		};

		// Remove existing menu object by menu id
		this.removeMenu = function(menuId) {
			// Validate that the menu exists
			this.validateMenuExistance(menuId);

			// Return the menu object
			delete this.menus[menuId];
		};

		// Add menu item object
		this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) {
			// Validate that the menu exists
			this.validateMenuExistance(menuId);

			// Push new menu item
			this.menus[menuId].items.push({
				title: menuItemTitle,
				link: menuItemURL,
				menuItemType: menuItemType || 'item',
				menuItemClass: menuItemType,
				uiRoute: menuItemUIRoute || ('/' + menuItemURL),
				isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic),
				roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles),
				position: position || 0,
				items: [],
				shouldRender: shouldRender
			});

			// Return the menu object
			return this.menus[menuId];
		};

		// Add submenu item object
		this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) {
			// Validate that the menu exists
			this.validateMenuExistance(menuId);

			// Search for menu item
			for (var itemIndex in this.menus[menuId].items) {
				if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) {
					// Push new submenu item
					this.menus[menuId].items[itemIndex].items.push({
						title: menuItemTitle,
						link: menuItemURL,
						uiRoute: menuItemUIRoute || ('/' + menuItemURL),
						isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic),
						roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles),
						position: position || 0,
						shouldRender: shouldRender
					});
				}
			}

			// Return the menu object
			return this.menus[menuId];
		};

		// Remove existing menu object by menu id
		this.removeMenuItem = function(menuId, menuItemURL) {
			// Validate that the menu exists
			this.validateMenuExistance(menuId);

			// Search for menu item to remove
			for (var itemIndex in this.menus[menuId].items) {
				if (this.menus[menuId].items[itemIndex].link === menuItemURL) {
					this.menus[menuId].items.splice(itemIndex, 1);
				}
			}

			// Return the menu object
			return this.menus[menuId];
		};

		// Remove existing menu object by menu id
		this.removeSubMenuItem = function(menuId, submenuItemURL) {
			// Validate that the menu exists
			this.validateMenuExistance(menuId);

			// Search for menu item to remove
			for (var itemIndex in this.menus[menuId].items) {
				for (var subitemIndex in this.menus[menuId].items[itemIndex].items) {
					if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) {
						this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1);
					}
				}
			}

			// Return the menu object
			return this.menus[menuId];
		};

		//Adding the topbar menu
		this.addMenu('topbar');
	}
]);