/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var EventEmitter = require( 'events' ).EventEmitter;
var inherit = require( '@stdlib/utils/inherit' );
var defineProperty = require( '@stdlib/utils/define-property' );
var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
var tic = require( '@stdlib/time/tic' );
var toc = require( '@stdlib/time/toc' );
var run = require( './run.js' );
var exit = require( './exit.js' );
var ended = require( './ended.js' );
var assert = require( './assert.js' );
var comment = require( './comment.js' );
var skip = require( './skip.js' );
var todo = require( './todo.js' );
var fail = require( './fail.js' );
var pass = require( './pass.js' );
var ok = require( './ok.js' );
var notOk = require( './not_ok.js' );
var equal = require( './equal.js' );
var notEqual = require( './not_equal.js' );
var deepEqual = require( './deep_equal.js' );
var notDeepEqual = require( './not_deep_equal.js' );
var end = require( './end.js' );


// MAIN //

/**
* Benchmark constructor.
*
* @constructor
* @param {string} name - benchmark name
* @param {Options} opts - benchmark options
* @param {boolean} opts.skip - boolean indicating whether to skip a benchmark
* @param {PositiveInteger} opts.iterations - number of iterations
* @param {PositiveInteger} opts.timeout - number of milliseconds before a benchmark automatically fails
* @param {Function} [benchmark] - function containing benchmark code
* @returns {Benchmark} Benchmark instance
*
* @example
* var bench = new Benchmark( 'beep', function benchmark( b ) {
*     var x;
*     var i;
*     b.comment( 'Running benchmarks...' );
*     b.tic();
*     for ( i = 0; i < b.iterations; i++ ) {
*         x = Math.sin( Math.random() );
*         if ( x !== x ) {
*             b.ok( false, 'should not return NaN' );
*         }
*     }
*     b.toc();
*     if ( x !== x ) {
*         b.ok( false, 'should not return NaN' );
*     }
*     b.comment( 'Finished running benchmarks.' );
*     b.end();
* });
*/
function Benchmark( name, opts, benchmark ) {
	var hasTicked;
	var hasTocked;
	var self;
	var time;
	if ( !( this instanceof Benchmark ) ) {
		return new Benchmark( name, opts, benchmark );
	}
	self = this;
	hasTicked = false;
	hasTocked = false;

	EventEmitter.call( this );

	// Private properties:
	setReadOnly( this, '_benchmark', benchmark );
	setReadOnly( this, '_skip', opts.skip );

	defineProperty( this, '_ended', {
		'configurable': false,
		'enumerable': false,
		'writable': true,
		'value': false
	});

	defineProperty( this, '_running', {
		'configurable': false,
		'enumerable': false,
		'writable': true,
		'value': false
	});

	defineProperty( this, '_exited', {
		'configurable': false,
		'enumerable': false,
		'writable': true,
		'value': false
	});

	defineProperty( this, '_count', {
		'configurable': false,
		'enumerable': false,
		'writable': true,
		'value': 0
	});

	// Read-only:
	setReadOnly( this, 'name', name );
	setReadOnly( this, 'tic', start );
	setReadOnly( this, 'toc', stop );
	setReadOnly( this, 'iterations', opts.iterations );
	setReadOnly( this, 'timeout', opts.timeout );

	return this;

	/**
	* Starts a benchmark timer.
	*
	* ## Notes
	*
	* -   Using a scoped variable prevents nefarious mutation by bad actors hoping to manipulate benchmark results.
	* -   The one attack vector which remains is manipulation of the `require` cache for `tic` and `toc`.
	* -   One way to combat cache manipulation is by comparing the checksum of `Function#toString()` against known values.
	*
	* @private
	*/
	function start() {
		if ( hasTicked ) {
			self.fail( '.tic() called more than once' );
		} else {
			self.emit( 'tic' );
			hasTicked = true;
			time = tic();
		}
	}

	/**
	* Stops a benchmark timer.
	*
	* @private
	* @returns {void}
	*/
	function stop() {
		var elapsed;
		var secs;
		var rate;
		var out;

		if ( hasTicked === false ) {
			return self.fail( '.toc() called before .tic()' );
		}
		elapsed = toc( time );
		if ( hasTocked ) {
			return self.fail( '.toc() called more than once' );
		}
		hasTocked = true;
		self.emit( 'toc' );

		secs = elapsed[ 0 ] + ( elapsed[ 1 ]/1e9 );
		rate = self.iterations / secs;

		out = {
			'ok': true,
			'operator': 'result',
			'iterations': self.iterations,
			'elapsed': secs,
			'rate': rate
		};
		self.emit( 'result', out );
	}
}

/*
* Inherit from the `EventEmitter` prototype.
*/
inherit( Benchmark, EventEmitter );

/**
* Runs a benchmark.
*
* @private
* @name run
* @memberof Benchmark.prototype
* @type {Function}
*/
setReadOnly( Benchmark.prototype, 'run', run );

/**
* Forcefully ends a benchmark.
*
* @private
* @name exit
* @memberof Benchmark.prototype
* @type {Function}
*/
setReadOnly( Benchmark.prototype, 'exit', exit );

/**
* Returns a `boolean` indicating if a benchmark has ended.
*
* @private
* @name ended
* @memberof Benchmark.prototype
* @type {Function}
* @returns {boolean} boolean indicating if a benchmark has ended
*/
setReadOnly( Benchmark.prototype, 'ended', ended );

/**
* Generates an assertion.
*
* @private
* @name _assert
* @memberof Benchmark.prototype
* @type {Function}
* @param {boolean} ok - assertion outcome
* @param {Options} opts - options
*/
setReadOnly( Benchmark.prototype, '_assert', assert );

/**
* Writes a comment.
*
* @name comment
* @memberof Benchmark.prototype
* @type {Function}
* @param {string} msg - comment message
*/
setReadOnly( Benchmark.prototype, 'comment', comment );

/**
* Generates an assertion which will be skipped.
*
* @name skip
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} value - value
* @param {string} msg - message
*/
setReadOnly( Benchmark.prototype, 'skip', skip );

/**
* Generates an assertion which should be implemented.
*
* @name todo
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} value - value
* @param {string} msg - message
*/
setReadOnly( Benchmark.prototype, 'todo', todo );

/**
* Generates a failing assertion.
*
* @name fail
* @memberof Benchmark.prototype
* @type {Function}
* @param {string} msg - message
*/
setReadOnly( Benchmark.prototype, 'fail', fail );

/**
* Generates a passing assertion.
*
* @name pass
* @memberof Benchmark.prototype
* @type {Function}
* @param {string} msg - message
*/
setReadOnly( Benchmark.prototype, 'pass', pass );

/**
* Asserts that a `value` is truthy.
*
* @name ok
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} value - value
* @param {string} [msg] - message
*/
setReadOnly( Benchmark.prototype, 'ok', ok );

/**
* Asserts that a `value` is falsy.
*
* @name notOk
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} value - value
* @param {string} [msg] - message
*/
setReadOnly( Benchmark.prototype, 'notOk', notOk );

/**
* Asserts that `actual` is strictly equal to `expected`.
*
* @name equal
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} actual - actual value
* @param {*} expected - expected value
* @param {string} [msg] - message
*/
setReadOnly( Benchmark.prototype, 'equal', equal );

/**
* Asserts that `actual` is not strictly equal to `expected`.
*
* @name notEqual
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} actual - actual value
* @param {*} expected - expected value
* @param {string} [msg] - message
*/
setReadOnly( Benchmark.prototype, 'notEqual', notEqual );

/**
* Asserts that `actual` is deeply equal to `expected`.
*
* @name deepEqual
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} actual - actual value
* @param {*} expected - expected value
* @param {string} [msg] message
*/
setReadOnly( Benchmark.prototype, 'deepEqual', deepEqual );

/**
* Asserts that `actual` is not deeply equal to `expected`.
*
* @name notDeepEqual
* @memberof Benchmark.prototype
* @type {Function}
* @param {*} actual - actual value
* @param {*} expected - expected value
* @param {string} [msg] message
*/
setReadOnly( Benchmark.prototype, 'notDeepEqual', notDeepEqual );

/**
* Ends a benchmark.
*
* @name end
* @memberof Benchmark.prototype
* @type {Function}
*/
setReadOnly( Benchmark.prototype, 'end', end );


// EXPORTS //

module.exports = Benchmark;
