'use strict';
/**
 * @module br/test/TimeUtility
 */
var Errors = require('br/Errors');
/**
 * @private
 * @class
 * @alias module:br/test/TimeUtility
 *
 * @classdesc
 * Utility class containing static methods that can be useful for controlling time in tests.
 */
var TimeUtility = {};
/** @private */
TimeUtility.TIMER_ID = 0;
/** @private */
TimeUtility.MANUAL_TIME_MODE = 'Manual';
/** @private */
TimeUtility.NEXT_STEP_TIME_MODE = 'NextStep';
/** @private */
TimeUtility.timeMode = TimeUtility.NEXT_STEP_TIME_MODE;
/** @private */
TimeUtility.pCapturedTimerFunctionArgs = [];
/** @private */
TimeUtility._bHasReplacedTimerFunctions = false;
/** @private */
TimeUtility.bCaptureTimeoutAndIntervals = true;
/** @private */
TimeUtility.fCapturedTimersSort = function(firstFunction, secondFunction) {
	return firstFunction[1] - secondFunction[1];
};
/**
 * Reset this TimeUtility to its original state. Useful for testing.
 * @private
 */
TimeUtility.reset = function() {
	this.timeMode = this.NEXT_STEP_TIME_MODE;
	this.bCaptureTimeoutAndIntervals = true;
	this.clearCapturedFunctions();
	this.releaseTimerFunctions();
};
/**
 * Overrides the default <code>setTimeout</code> and <code>setInterval</code> methods. This allows the storing of all
 *  functions passed in to those methods.
 */
TimeUtility.captureTimerFunctions = function() {
	if (this.bCaptureTimeoutAndIntervals && this._bHasReplacedTimerFunctions === false) {
		this.ORIGINAL_SETTIMEOUT_FUNCTION = window.setTimeout;
		this.ORIGINAL_SETINTERVAL_FUNCTION = window.setInterval;
		this.ORIGINAL_CLEARTIMEOUT_FUNCTION = window.clearTimeout;
		this.ORIGINAL_CLEARINTERVAL_FUNCTION = window.clearInterval;
		window.setTimeout = TimeUtility.fCaptureArguments;
		window.setInterval = TimeUtility.fCaptureArguments;
		window.clearTimeout = TimeUtility.fClearTimer;
		window.clearInterval = TimeUtility.fClearTimer;
		this._bHasReplacedTimerFunctions = true;
	}
};
/**
 * @return {Array} A list of <code>argument</code> objects that were passed into <code>setTimeout</code> and
 *  <code>setInterval</code>.
 */
TimeUtility.getCapturedFunctions = function() {
	var capturedFunctions = this.pCapturedTimerFunctionArgs.slice();
	return capturedFunctions.sort(this.fCapturedTimersSort);
};
/** @private */
TimeUtility.clearCapturedFunctions = function() {
	this.pCapturedTimerFunctionArgs.length = 0;
};
/**
 * Execute all captured functions that are set to be triggered within the passed in millisecond time value. If no value
 *  is passed, this will execute all captured functions.
 */
TimeUtility.executeCapturedFunctions = function(nMsToExecuteTo) {
	var capturedFunction, innerCapturedFunction;
	this.pCapturedTimerFunctionArgs.sort(this.fCapturedTimersSort);
	for (var idx = 0; idx < this.pCapturedTimerFunctionArgs.length; idx++) {
		capturedFunction = this.pCapturedTimerFunctionArgs[idx];
		if (nMsToExecuteTo == null || capturedFunction[1] <= nMsToExecuteTo) {
			var timerArgsLength = this.pCapturedTimerFunctionArgs.length;
			capturedFunction[0]();
			
			for (var idy = timerArgsLength; idy < this.pCapturedTimerFunctionArgs.length; idy++) {
				innerCapturedFunction = this.pCapturedTimerFunctionArgs[idy];
				innerCapturedFunction[1] += capturedFunction[1];
			}
			
			this.pCapturedTimerFunctionArgs.splice(idx, 1);
			idx--;
		} else {
			capturedFunction[1] -= nMsToExecuteTo;
		}
	}
};
/**
 * Execute all captured functions if we are in NEXT_STEP_TIME_MODE. All functions will be cleared even if an error is
 *  thrown, although not all functions will be executed. Returns false if we are not in NEXT_STEP_TIME_MODE
 */
TimeUtility.nextStep = function() {
	if (this.timeMode === this.NEXT_STEP_TIME_MODE) {
		try {
			this.executeCapturedFunctions();
		} finally {
			this.clearCapturedFunctions();
		}
		return true;
	} else {
		return false;
	}
};
/**
 * Sets the timer mode which controls when captured timeouts and intervals run.
 */
TimeUtility.setTimeMode = function(timeMode) {
	if (timeMode === this.MANUAL_TIME_MODE || timeMode === this.NEXT_STEP_TIME_MODE) {
		this.timeMode = timeMode;
	} else {
		throw new Errors.InvalidTestError('Incorrect time mode (' + timeMode + ') set on TimeUtility.');
	}
};
/** @private */
TimeUtility.releaseTimerFunctions = function() {
	if (this._bHasReplacedTimerFunctions) {
		window.setTimeout = this.ORIGINAL_SETTIMEOUT_FUNCTION;
		window.setInterval = this.ORIGINAL_SETINTERVAL_FUNCTION;
		window.clearTimeout = this.ORIGINAL_CLEARTIMEOUT_FUNCTION;
		window.clearInterval = this.ORIGINAL_CLEARINTERVAL_FUNCTION;
		this._bHasReplacedTimerFunctions = false;
		delete this.ORIGINAL_SETTIMEOUT_FUNCTION;
		delete this.ORIGINAL_SETINTERVAL_FUNCTION;
		delete this.ORIGINAL_CLEARTIMEOUT_FUNCTION;
		delete this.ORIGINAL_CLEARINTERVAL_FUNCTION;
	}
};
/** @private */
TimeUtility.fCaptureArguments = function() {
	arguments.nTimerId = TimeUtility.TIMER_ID++;
	TimeUtility.pCapturedTimerFunctionArgs.push(arguments);
	return arguments.nTimerId;
};
/** @private */
TimeUtility.fClearTimer = function(timerId) {
	var capturedFunctions = TimeUtility.pCapturedTimerFunctionArgs;
	capturedFunctions = capturedFunctions.filter(function(capturedFunction){
		return capturedFunction.nTimerId !== timerId;
	});
	TimeUtility.pCapturedTimerFunctionArgs = capturedFunctions;
};
module.exports = TimeUtility;