/* @flow weak */
/**
 * @memberof Marbles
 * @mixin
 * @desc Manages a state object. You must define `state` {Object} and `__changeListeners` {Array} on the object this is mixed into.
 */
var State = {
	/**
	 * @method
	 * @param {function} handler Function to call when the state object changes
	 */
	addChangeListener: function (handler) {
		this.__changeListeners.push(handler);
	},

	/**
	 * @method
	 * @param {function} handler
	 * @desc Prevents handler from being called for future changes
	 */
	removeChangeListener: function (handler) {
		this.__changeListeners = this.__changeListeners.filter(function (fn) {
			return fn !== handler;
		});
	},

	/**
	 * @method
	 * @param {function} changeFn
	 * @desc Calls `willChange`, the passed in function, `handleChange`, then `didChange`
	 */
	withChange: function (changeFn) {
		this.willChange();
		changeFn.call(this);
		this.handleChange();
		this.didChange();
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Copies properties of newState to the existing state object
	 */
	setState: function (newState) {
		this.withChange(function () {
			var state = this.state;
			Object.keys(newState).forEach(function (key) {
				state[key] = newState[key];
			});
		});
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Same as setState, but waits up to 10ms for more changes to occur before calling change listeners
	 */
	setStateWithDelay: function (newState) {
		this.willChange();
		var state = this.state;
		Object.keys(newState).forEach(function (key) {
			state[key] = newState[key];
		});
		this.handleChangeWithDelay();
	},

	handleChangeWithDelay: function () {
		clearTimeout(this.__handleChangeTimeout);
		this.__handleChangeTimeout = setTimeout(function () {
			this.handleChangeDelayed();
		}.bind(this), 2);
		if ( !this.__handleChangeMaxTimeout ) {
			this.__handleChangeMaxTimeout = setTimeout(function () {
				this.handleChangeDelayed();
			}.bind(this), 10);
		}
	},

	handleChangeDelayed: function () {
		clearTimeout(this.__handleChangeMaxTimeout);
		clearTimeout(this.__handleChangeTimeout);
		this.handleChange();
		this.didChange();
	},

	/**
	 * @method
	 * @param {Object} newState
	 * @desc Replaces the existing state object with newState
	 */
	replaceState: function (newState) {
		this.withChange(function () {
			this.state = newState;
		});
	},

	handleChange: function () {
		this.__changeListeners.forEach(function (handler) {
			handler();
		});
	},

	/**
	 * @method
	 * @desc Called before state object is mutated
	 */
	willChange: function () {},

	/**
	 * @method
	 * @desc Called after state object is mutated
	 */
	didChange: function () {}
};

export default State;

@@ -1267,32 +1267,63 @@
 bject%7D newState%0A
+%09 * @param %7BNumber%7D maxTimeout%0A
 %09 * @desc Same a
@@ -1448,32 +1448,44 @@
 nction (newState
+, maxTimeout
 ) %7B%0A%09%09this.willC
@@ -1634,16 +1634,26 @@
 thDelay(
+maxTimeout
 );%0A%09%7D,%0A%0A
@@ -1678,35 +1678,78 @@
 elay: function (
-) %7B
+maxTimeout) %7B%0A%09%09maxTimeout = maxTimeout %7C%7C 10;
 %0A%09%09clearTimeout(
@@ -2035,18 +2035,26 @@
 (this), 
-10
+maxTimeout
 );%0A%09%09%7D%0A%09

Allow setting max timeout for setStateWithDelay