(function () {

	if (typeof Prism === 'undefined') {
		return;
	}

	/**
	 * @callback ClassMapper
	 * @param {string} className
	 * @param {string} language
	 * @returns {string}
	 *
	 * @callback ClassAdder
	 * @param {ClassAdderEnvironment} env
	 * @returns {undefined | string | string[]}
	 *
	 * @typedef ClassAdderEnvironment
	 * @property {string} language
	 * @property {string} type
	 * @property {string} content
	 */

	// options

	/** @type {ClassAdder | undefined} */
	var adder;
	/** @type {ClassMapper | undefined} */
	var mapper;
	/** @type {string} */
	var prefixString = '';


	/**
	 * @param {string} className
	 * @param {string} language
	 */
	function apply(className, language) {
		return prefixString + (mapper ? mapper(className, language) : className);
	}


	Prism.plugins.customClass = {
		/**
		 * Sets the function which can be used to add custom aliases to any token.
		 *
		 * @param {ClassAdder} classAdder
		 */
		add: function (classAdder) {
			adder = classAdder;
		},
		/**
		 * Maps all class names using the given object or map function.
		 *
		 * This does not affect the prefix.
		 *
		 * @param {Object<string, string> | ClassMapper} classMapper
		 */
		map: function map(classMapper) {
			if (typeof classMapper === 'function') {
				mapper = classMapper;
			} else {
				mapper = function (className) {
					return classMapper[className] || className;
				};
			}
		},
		/**
		 * Adds the given prefix to all class names.
		 *
		 * @param {string} string
		 */
		prefix: function prefix(string) {
			prefixString = string || '';
		},
		/**
		 * Applies the current mapping and prefix to the given class name.
		 *
		 * @param {string} className A single class name.
		 * @param {string} language The language of the code that contains this class name.
		 *
		 * If the language is unknown, pass `"none"`.
		 */
		apply: apply
	};

	Prism.hooks.add('wrap', function (env) {
		if (adder) {
			var result = adder({
				content: env.content,
				type: env.type,
				language: env.language
			});

			if (Array.isArray(result)) {
				env.classes.push.apply(env.classes, result);
			} else if (result) {
				env.classes.push(result);
			}
		}

		if (!mapper && !prefixString) {
			return;
		}

		env.classes = env.classes.map(function (c) {
			return apply(c, env.language);
		});
	});

}());