/**
 * @defgroup js_classes
 */

/**
 * @file js/classes/Helper.js
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class Helper
 * @ingroup js_controllers
 *
 * @brief PKP helper methods
 */
(function($) {

	// Create PKP namespaces.
	/** @type {Object} */
	$.pkp = $.pkp || { };


	/** @type {Object} */
	$.pkp.classes = $.pkp.classes || { };


	/** @type {Object} */
	$.pkp.controllers = $.pkp.controllers || { };


	/** @type {Object} */
	$.pkp.plugins = $.pkp.plugins || {};


	/** @type {Object} */
	$.pkp.plugins.blocks = $.pkp.plugins.blocks || {};


	/** @type {Object} */
	$.pkp.plugins.generic = $.pkp.plugins.generic || {};


	/** @type {Object} */
	$.pkp.plugins.pubIds = $.pkp.plugins.pubIds || {};


	/** @type {Object} */
	$.pkp.plugins.importexport = $.pkp.plugins.importexport || {};



	/**
	 * Helper singleton
	 * @constructor
	 *
	 * @extends $.pkp.classes.ObjectProxy
	 */
	$.pkp.classes.Helper = function() {
		throw new Error('Trying to instantiate the Helper singleton!');
	};


	//
	// Private class constants
	//
	/**
	 * Characters available for UUID generation.
	 * @const
	 * @private
	 * @type {Array}
	 */
	$.pkp.classes.Helper.CHARS_ = ['0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
		'abcdefghijklmnopqrstuvwxyz'].join('').split('');


	//
	// Public static helper methods
	//
	/**
	 * Generate a random UUID.
	 *
	 * Original code thanks to Robert Kieffer <robert@broofa.com>,
	 * http://www.broofa.com, adapted by PKP.
	 *
	 * Copyright (c) 2010 Robert Kieffer
	 * Copyright (c) 2014-2021 Simon Fraser University
	 * Copyright (c) 2010-2021 John Willinsky
	 * Distributed under the GNU GPL v3 and MIT licenses. For full
	 * terms see the file docs/COPYING.
	 *
	 * See discussion of randomness versus uniqueness:
	 * http://www.broofa.com/2008/09/javascript-uuid-function/
	 *
	 * @return {string} an RFC4122v4 compliant UUID.
	 */
	$.pkp.classes.Helper.uuid = function() {
		var chars = $.pkp.classes.Helper.CHARS_, uuid = new Array(36), rnd = 0, r, i;
		for (i = 0; i < 36; i++) {
			if (i == 8 || i == 13 || i == 18 || i == 23) {
				uuid[i] = '-';
			} else if (i == 14) {
				uuid[i] = '4';
			} else {
				/*jslint bitwise: true*/
				if (rnd <= 0x02) {
					rnd = 0x2000000 + (Math.random() * 0x1000000) | 0;
				}
				r = rnd & 0xf;
				rnd = rnd >> 4;
				uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
				/*jslint bitwise: false*/
			}
		}
		return uuid.join('');
	};


	/**
	 * Let one object inherit from another.
	 *
	 * Example:
	 *  $.pkp.classes.Parent = function() {...};
	 *  $.pkp.classes.Child = function() {...};
	 *  $.pkp.classes.Helper.inherits($.pkp.classes.Child, $.pkp.classes.Parent);
	 *
	 * @param {Function} Child Constructor of the child object.
	 * @param {Function} Parent Constructor of the parent object.
	 */
	$.pkp.classes.Helper.inherits = function(Child, Parent) {
		// Use an empty temporary object to avoid
		// calling a potentially costly constructor
		// on the parent object which also may have
		// undesired side effects. Also avoids instantiating
		// a potentially big object.
		/** @constructor */ var Temp = function() {};
		Temp.prototype = Parent.prototype;

		// Provide a way to reach the parent's
		// method implementations even after
		// overriding them in the child object.
		Child.parent_ = Parent.prototype;

		// Let the child object inherit from
		// the parent object.
		Child.prototype = new Temp();

		// Need to fix the child constructor because
		// it get's lost when setting the prototype
		// to an object instance.
		Child.prototype.constructor = Child;

		// Make sure that we can always call the parent object's
		// constructor without coupling the child constructor
		// to it. This should work even when the parent inherits
		// directly from an Object instance (i.e. the parent's
		// prototype was set like this: Parent.prototype = {...})
		// which wipes out the original constructor.
		if (Parent.prototype.constructor == Object.prototype.constructor) {
			Parent.prototype.constructor = Parent;
		}
	};


	/**
	 * Introduce a central object factory that maintains some
	 * level of indirection so that we can enrich objects, e.g.
	 * with aspects, provide different runtime-implementations
	 * of objects, distinguish between singletons and prototypes
	 * or even implement dependency injection if we want to.
	 *
	 * The standard implementation has a 'convention over
	 * configuration' approach that assumes that an object's
	 * name corresponds to the name of a constructor within
	 * the global jQuery namespace ($).
	 *
	 * The factory also helps us to avoid the common pitfall to
	 * use a constructor without the 'new' keyword.
	 *
	 * @param {string} objectName The name of an object.
	 * @param {Array} args The arguments to be passed
	 *  into the object's constructor.
	 * @return {$.pkp.classes.ObjectProxy} the instantiated object.
	 */
	$.pkp.classes.Helper.objectFactory = function(objectName, args) {
		var ObjectConstructor, ObjectProxyInstance, objectInstance;

		// Resolve the object name.
		ObjectConstructor = $.pkp.classes.Helper.resolveObjectName(objectName);

		// Create a new proxy constructor instance.
		ObjectProxyInstance = $.pkp.classes.Helper.getObjectProxyInstance();

		// Copy static members over from the object proxy. (This may
		// overwrite the proxy constructor's prototype in some
		// browsers but we don't care because we'll replace the prototype
		// anyway when we inherit.)
		$.extend(true, ObjectProxyInstance, $.pkp.classes.ObjectProxy);

		// Let the proxy inherit from the proxied object.
		$.pkp.classes.Helper.inherits(ObjectProxyInstance, ObjectConstructor);

		// Enrich the new proxy constructor prototype with proxy object
		// prototype members.
		$.extend(true, ObjectProxyInstance.prototype,
				$.pkp.classes.ObjectProxy.prototype);

		// Instantiate the proxy with the proxied object.
		objectInstance = new ObjectProxyInstance(objectName, args);
		return objectInstance;
	};


	/**
	 * Resolves the given object name to an object implementation
	 * (or better to it's constructor).
	 * @param {string} objectName The object name to resolve.
	 * @return {Function} The constructor of the object.
	 */
	$.pkp.classes.Helper.resolveObjectName = function(objectName) {
		var objectNameParts, i, functionName, ObjectConstructor;

		// Currently only objects in the $ namespace are
		// supported.
		objectNameParts = objectName.split('.');
		if (objectNameParts.shift() != '$') {
			throw new Error(['Namespace "', objectNameParts[0], '" for object "',
				objectName, '" is currently not supported!'].join(''));
		}

		// Make sure that we actually have a constructor name
		// (starts with an upper case letter).
		functionName = objectNameParts[objectNameParts.length - 1];
		if (functionName.charAt(0).toUpperCase() !== functionName.charAt(0)) {
			throw new Error(['The name "', objectName, '" does not point to a',
				'constructor which must always be upper case!'].join(''));
		}

		// Run through the namespace and identify the constructor.
		ObjectConstructor = $;
		for (i in objectNameParts) {
			ObjectConstructor = ObjectConstructor[objectNameParts[i]];
			if (ObjectConstructor === undefined) {
				throw new Error(['Constructor for object "', objectName, '" not found!']
						.join(''));
			}
		}

		// Check that the constructor actually is a function.
		if (!$.isFunction(ObjectConstructor)) {
			throw new Error(['The name "', objectName, '" does not point to a',
				'constructor which must always be a function!'].join());
		}

		return ObjectConstructor;
	};


	/**
	 * Create a new instance of a proxy constructor.
	 *
	 * NB: We do this in a separate closure to avoid
	 * memory leaks.
	 *
	 * @return {Function} a new proxy instance.
	 */
	$.pkp.classes.Helper.getObjectProxyInstance = function() {
		// Create a new proxy constructor so that proxies
		// do not interfere with each other.
		/**
		 * @constructor
		 *
		 * @param {string} objectName The name of the proxied
		 *  object.
		 * @param {Array} args The arguments to be passed to
		 *  the constructor of the proxied object.
		 */
		var proxyConstructor = function(objectName, args) {
			// Set the internal object name.
			this.objectName_ = objectName;

			// Call the constructor of the proxied object.
			this.parent.apply(this, args);
		};

		// Declare properties/methods used in the constructor for the
		// closure compiler. These will later be overwritten by the
		// true implementation.
		/**
		 * @private
		 * @type {string} The object name of this object.
		 */
		proxyConstructor.objectName_ = '';

		/**
		 * @param {*=} opt_methodName The name of the method to
		 *  be found. Do not set when calling this method from a
		 *  constructor!
		 * @param {...*} var_args Arguments to be passed to the
		 *  parent method.
		 * @return {*} The return value of the parent method.
		 */
		proxyConstructor.prototype.parent = function(opt_methodName, var_args) {};

		return proxyConstructor;
	};


	/**
	 * Inject (mix in) an interface into an object.
	 * @param {Function} Constructor The target object's constructor.
	 * @param {string} mixinObjectName The object name of interface
	 *  that can be resolved to an interface implementation by the
	 *  object factory.
	 */
	$.pkp.classes.Helper.injectMixin = function(Constructor, mixinObjectName) {
		// Retrieve an instance of the mix-in interface implementation.
		var mixin = $.pkp.classes.Helper.objectFactory(mixinObjectName, []);

		// Inject the mix-in into the target constructor.
		$.extend(true, Constructor, mixin);
	};


	/**
	 * A function currying implementation borrowed from Google Closure.
	 * @param {Function} fn A function to partially apply.
	 * @param {Object} context Specifies the object which |this| should
	 *  point to when the function is run. If the value is null or undefined, it
	 *  will default to the global object.
	 * @param {...*} var_args Additional arguments that are partially
	 *  applied to the function.
	 * @return {!Function} A partially-applied form of the function bind() was
	 *  invoked as a method of.
	 */
	$.pkp.classes.Helper.curry = function(fn, context, var_args) {
		if (arguments.length > 2) {
			var boundArgs, newArgs;
			boundArgs = Array.prototype.slice.call(arguments, 2);
			return function() {
				// Prepend the bound arguments to the current arguments.
				newArgs = Array.prototype.slice.call(arguments);
				Array.prototype.unshift.apply(newArgs, boundArgs);
				return fn.apply(context, newArgs);
			};
		} else {
			return function() {
				return fn.apply(context, arguments);
			};
		}
	};


	/**
	 * A function that takes care of escaping @ character which could be interpreted
	 * as CSS notation. This is due to the fact that jQuery uses CSS syntax for
	 * selecting elements. These characters must be escaped by placing two
	 * backslashes in front of them.
	 * @param {string} elementId jQuery element selector
	 * @return {string}
	 */
	$.pkp.classes.Helper.escapeJQuerySelector = function(elementId) {
		return elementId.replace('@', '\\@');
	};


}(jQuery));
