(function (root) {
	'use strict';

	function Glop() {
		return applyInstanceProps({});
	}

	function applyInstanceProps(instance) {
		var handlers = {};

		instance.on = on.bind(null, handlers, instance, false);
		instance.one = on.bind(null, handlers, instance, true);
		instance.only = only.bind(null, handlers, instance, false);
		instance.onely = only.bind(null, handlers, instance, true);
		instance.off = off.bind(null, handlers, instance);
		instance.emit = emit.bind(null, handlers, instance);
		instance.emitter = emitter.bind(null, handlers, instance);
		instance.setup = setup.bind(null, handlers, instance);
		instance.setLogger = setLogger.bind(null, handlers, instance);
		instance.log = function () {};
		instance.clear = clear.bind(null, handlers, instance);

		return instance;
	}

	function setup(handlers, instance, eventNames) {
		instance.log('Setup: Clearing all event names and handlers');
		Object.keys(handlers).forEach(function (eventName) {
			delete handlers[eventName];
		});

		eventNames.forEach(function (eventName) {
			handlers[eventName] = [];
		});

		instance.log('Setup: event names set', eventNames);
		return instance;
	}

	function on(handlers, instance, one, eventName, eventHandler) {
		if (typeof eventName === 'object') {
			instance.log('On' + (one ? 'e' : '') + ': setting multiple handlers', eventName, eventHandler);
			eventName.forEach(function (singleEventName) {
				on(handlers, instance, one, singleEventName, eventHandler);
			});

		} else if (typeof handlers[eventName] === 'object') {
			instance.log('On' + (one ? 'e' : '') + ': setting handler for ' + eventName, eventHandler);
			handlers[eventName].unshift({
				fn: eventHandler,
				one: one
			});

		} else {
			instance.log('ERROR: On: invalid event name: ' + eventName);
		}

		return instance;
	}

	function only(handlers, instance, one, eventName, eventHandler) {
		if (typeof eventName === 'object') {
			instance.log('On' + (one ? 'e' : '') + 'ly: setting multiple handlers', eventName, eventHandler);
			eventName.forEach(function (singleEventName) {
				only(handlers, instance, one, singleEventName, eventHandler);
			});

		} else if (typeof handlers[eventName] === 'object') {
			instance.log('On' + (one ? 'e' : '') + 'ly: clearing handlers for ' + eventName);
			off(handlers, instance, eventName);

			instance.log('On' + (one ? 'e' : '') + 'ly: setting handler for ' + eventName + ' through "on"');
			return on(handlers, instance, one, eventName, eventHandler);

		} else {
			instance.log('ERROR: On' + (one ? 'e' : '') + 'ly: invalid event name: ' + eventName);
		}

		return instance;
	}

	function emitter(handlers, instance, eventName) {
		instance.log( 'Emitter: generating emitter for ' + eventName );
		if (typeof handlers[eventName] === 'object') {
			return emit.bind(null, handlers, instance, eventName);
		}
	}

	function emit(handlers, instance, eventName) {
		if (typeof eventName === 'object') {
			instance.log( 'Emit: Emitting multiple event names', eventName );
			eventName.forEach(function (singleEventName) {
				var dataArgs = Array.prototype.slice.call(arguments).slice(3);
				emit.apply(null, [handlers, instance, singleEventName].concat[dataArgs]);
			});

		} else if (typeof handlers[eventName] === 'object') {
			var dataArgs = Array.prototype.slice.call(arguments).slice(3);
			var i = handlers[eventName].length;

			instance.log.apply(null, ['Emit: emitting ' + eventName].concat(dataArgs));
			while (i--) {
				instance.log('Emit: Handler found for ' + eventName, handlers[eventName][i].fn);
				handlers[eventName][i].fn.apply(null, dataArgs);

				if (handlers[eventName][i].one) {
					instance.log('Emit: Removing single fire handler for ' + eventName, handlers[eventName][i].fn);
					handlers[eventName].splice(i, 1);
				}
			}

		} else {
			instance.log('ERROR: Emit: invalid event name: ' + eventName);
		}

		return instance;
	}

	function off(handlers, instance, eventName, eventHandler) {
		if (typeof eventName === 'object') {
			instance.log('Off: de-registering multiple event handlers', eventName, eventHandler);
			eventName.forEach(function (singleEventName) {
				off(handlers, instance, singleEventName, eventHandler);
			});

		} else if (typeof handlers[eventName] === 'object') {
			if (handlers[eventName] && !eventHandler) {
				instance.log('Off: de-registering all event handlers for ' + eventName);
				handlers[eventName] = [];

			} else if (handlers[eventName]) {
				instance.log('Off: looking for specific event handler for ' + eventName, eventHandler);
				var i = handlers[eventName].length;

				while (i--) {
					if (handlers[eventName][i].fn === eventHandler) {
						instance.log('Off: de-registering specific event handler for ' + eventName, eventHandler);
						handlers[eventName].splice(i, 1);
					}
				}
			}

		} else {
			instance.log('ERROR: Off: invalid event name: ' + eventName);
		}

		return instance;
	}

	function clear(handlers, instance) {
		Object.keys(handlers).forEach(function (eventName) {
			handlers[eventName] = [];
		});

		instance.log('Clear: all handlers cleared');
		return instance;
	}

	function setLogger(handlers, instance, logFunction) {
		if (typeof logFunction === 'function') {
			instance.log = logFunction;
			instance.log('Glop log initialized!');
		}
	}

	applyInstanceProps(Glop);

	if (typeof module === 'object') {
		module.exports = Glop;

	} else {
		root.Glop = Glop;
	}

}).call(null, this);