import _ from 'underscore';

/*
* Callback hooks provide an easy way to add extra steps to common operations.
* @namespace RocketChat.callbacks
*/

RocketChat.callbacks = {};

if (Meteor.isServer) {
	RocketChat.callbacks.showTime = true;
	RocketChat.callbacks.showTotalTime = true;
} else {
	RocketChat.callbacks.showTime = false;
	RocketChat.callbacks.showTotalTime = false;
}


/*
* Callback priorities
*/

RocketChat.callbacks.priority = {
	HIGH: -1000,
	MEDIUM: 0,
	LOW: 1000,
};

const getHooks = (hookName) => RocketChat.callbacks[hookName] || [];

/*
* Add a callback function to a hook
* @param {String} hook - The name of the hook
* @param {Function} callback - The callback function
*/

RocketChat.callbacks.add = function(hook, callback, priority, id = Random.id()) {
	if (!_.isNumber(priority)) {
		priority = RocketChat.callbacks.priority.MEDIUM;
	}
	callback.priority = priority;
	callback.id = id;
	RocketChat.callbacks[hook] = getHooks(hook);

	if (RocketChat.callbacks.showTime === true) {
		const err = new Error;
		callback.stack = err.stack;
	}

	if (RocketChat.callbacks[hook].find((cb) => cb.id === callback.id)) {
		return;
	}
	RocketChat.callbacks[hook].push(callback);
	RocketChat.callbacks[hook] = _.sortBy(RocketChat.callbacks[hook], function(callback) {
		return callback.priority || RocketChat.callbacks.priority.MEDIUM;
	});
};


/*
* Remove a callback from a hook
* @param {string} hook - The name of the hook
* @param {string} id - The callback's id
*/

RocketChat.callbacks.remove = function(hook, id) {
	RocketChat.callbacks[hook] = getHooks(hook).filter((callback) => callback.id !== id);
};


/*
* Successively run all of a hook's callbacks on an item
* @param {String} hook - The name of the hook
* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
* @param {Object} [constant] - An optional constant that will be passed along to each callback
* @returns {Object} Returns the item after it's been through all the callbacks for this hook
*/

RocketChat.callbacks.run = function(hook, item, constant) {
	const callbacks = RocketChat.callbacks[hook];
	if (!callbacks || !callbacks.length) {
		return item;
	}

	let rocketchatHooksEnd;
	if (Meteor.isServer) {
		rocketchatHooksEnd = RocketChat.metrics.rocketchatHooks.startTimer({ hook, callbacks_length: callbacks.length });
	}

	let totalTime = 0;
	const result = callbacks.reduce(function(result, callback) {
		let rocketchatCallbacksEnd;
		if (Meteor.isServer) {
			rocketchatCallbacksEnd = RocketChat.metrics.rocketchatCallbacks.startTimer({ hook, callback: callback.id });
		}
		const time = RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true ? Date.now() : 0;

		const callbackResult = callback(result, constant);

		if (RocketChat.callbacks.showTime === true || RocketChat.callbacks.showTotalTime === true) {
			const currentTime = Date.now() - time;
			totalTime += currentTime;
			if (RocketChat.callbacks.showTime === true) {
				if (Meteor.isServer) {
					rocketchatCallbacksEnd();
					RocketChat.statsTracker.timing('callbacks.time', currentTime, [`hook:${ hook }`, `callback:${ callback.id }`]);
				} else {
					let stack = callback.stack && typeof callback.stack.split === 'function' && callback.stack.split('\n');
					stack = stack && stack[2] && (stack[2].match(/\(.+\)/) || [])[0];
					console.log(String(currentTime), hook, callback.id, stack);
				}
			}
		}
		return (typeof callbackResult === 'undefined') ? result : callbackResult;
	}, item);

	if (Meteor.isServer) {
		rocketchatHooksEnd();
	}

	if (RocketChat.callbacks.showTotalTime === true) {
		if (Meteor.isServer) {
			RocketChat.statsTracker.timing('callbacks.totalTime', totalTime, [`hook:${ hook }`]);
		} else {
			console.log(`${ hook }:`, totalTime);
		}
	}

	return result;

};


/*
* Successively run all of a hook's callbacks on an item, in async mode (only works on server)
* @param {String} hook - The name of the hook
* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks
* @param {Object} [constant] - An optional constant that will be passed along to each callback
*/

RocketChat.callbacks.runAsync = function(hook, item, constant) {
	const callbacks = RocketChat.callbacks[hook];
	if (Meteor.isServer && callbacks && callbacks.length) {
		Meteor.defer(function() {
			callbacks.forEach((callback) => callback(item, constant));
		});
	}
	return item;
};
