define([
	'Guard', 'Arrays',
	'Unit', 'UnitExpression'], 
function(Guard, Arrays, Unit, UnitExpression) {
	/**
	 * @classdesc
	 * DerivedUnits are composed of a [UnitExpression]{@link UnitExpression} and a scale factor.
	 * The UnitExpression encapsulates the combination of other Units
	 * that composes this DerivedUnit, and the scale represents the
	 * scale factor.
	 * @class
	 * @extends {Unit}
	 * @alias DerivedUnit
	 * @hideconstructor
	 * @param {string} name 
	 * @param {string} symbol 
	 * @param {number} scale 
	 * @param {Array<Term>} terms 
	 */
	function DerivedUnit(name, symbol, scale, terms) {
		Guard(terms, 'terms').isTruthy().isArray();
		if (terms.length === 0) {
			throw new Error("terms list was empty");
		}
		var compositeScale = scale * getScaleOfTerms(terms);
		this._terms = Arrays.frozenClone(terms);
		Unit.call(this, name, symbol, compositeScale);
		Object.freeze(this);
	}

	function getScaleOfTerms(terms) {
		return terms
		.map(function(term) {
			return Math.pow(term.unit().scale(), term.power());
		})
		.reduce(
			function(acc, factor) {
				return acc * factor;
			}, 
			1
		);
	}

	DerivedUnit.prototype = Object.create(Unit.prototype);
	Object.defineProperty(DerivedUnit.prototype, 'constructor', {
		value: DerivedUnit,
		enumerable: false,
		writable: true
	});

	var functions = 
	/**
	 * @lends DerivedUnit#
	 */
	{
		/**
		 * Returns the [Dimensions]{@link Dimensions} of the [expression]{@link UnitExpression}
		 * that defines this DerivedUnit.
		 * @override
		 * @method
		 * @see {DerivedUnit#expression}
		 */
		dimensions: function() {
			return this.expression().dimensions();
		},
		/**
		 * Always return true.
		 * @method
		 * @override
		 */
		isBaseUnit: function() {
			return false;
		},
		/**
		 * The UnitExpression that defines the DerivedUnit.
		 * For instance, the Joule is defined the Newton-meter per second squared.
		 * @method
		 * @override
		 */
		expression: function() {
			var terms = [];
			for(var i = 0; i < this._terms.length; i++) {
				var up = this._terms[i];
				terms[i] = up;
			}
			return new UnitExpression(terms);
		},
		/**
		 * Returns a string representation of this DerivedUnit,
		 * composed of its scale and the string representation of its
		 * expression().
		 * @override
		 * @method
		 */
		toString: function() {
			return this.symbol();
		}
	};

	Object.assign(DerivedUnit.prototype, functions);

	return DerivedUnit;
});