define([], function() {
	/**
	 * @classdesc
	 * Represents a dimensive quantity.  
	 * Quantities have two parts:
	 * 	1. value, or magnitude
	 *  2. UnitExpression, which indicates the units attached to the value.
	 * @class
	 * @alias Quantity
	 * @hideconstructor
	 * @param {UnitExpression} unitExpression - the UnitExpression associated with Quantity, such as "m/s".
	 * @param {number} value - the numeric value of the Quantity, such as `4.0`.
	 * @param {Steradian} Sr - the instance of Steradian with which this Quantity should be associated.
	 */
	function Quantity(unitExpression, value, Sr) {
		this._unitExpression = unitExpression;
		this._value = value;
		if (!Sr) {
			throw new Error("Sr was not an object.");
		}
		this.Sr = Sr;
		Object.freeze(this);
	}

	var ArithmeticOps = {
		add: function add(lhs, rhs) {
			var lhsUnits = lhs.units();
			var convertedRhs = rhs.convertTo(lhsUnits);
			var resultValue = lhs.value() + convertedRhs.value();
			var resultQuantity = quantity(resultValue, lhsUnits, lhs.Sr); 
			return resultQuantity;
		},
		subtract: function add(lhs, rhs) {
			var lhsUnits = lhs.units();
			var convertedRhs = rhs.convertTo(lhsUnits);
			var resultValue = lhs.value() - convertedRhs.value();
			var resultQuantity = quantity(resultValue, lhsUnits, lhs.Sr); 
			return resultQuantity;
		},
		multiply: function(lhs, rhs) { 
			var resultUnits = lhs.units().mult(rhs.units());
			var resultValue = lhs.value() * rhs.value();
			var resultQuantity = quantity(resultValue, resultUnits, lhs.Sr);
			return resultQuantity;
		},
		divide: function(lhs, rhs) { 
			var resultUnits = lhs.units().div(rhs.units());
			var resultValue = lhs.value() / rhs.value();
			var resultQuantity = quantity(resultValue, resultUnits, lhs.Sr);
			return resultQuantity;
		}
	};

	Quantity.prototype = 
	/**
	 * @lends Quantity#
	 */
	{
		/**
		 * Converts this Quantity to another Quantity in the given
		 * units or System.
		 * 
		 * `newUnits` may be the name of a defined Unit, the name of 
		 * a defined System, an instance of Unit, or an instance of
		 * System.
		 * 
		 * This method provides an OO-flavored API that directly 
		 * corresponds to the Functional-flavored `Sr.convert(quantity, targetUnitsOrSystem)`.
		 * @method
		 * @param {string|Unit|UnitExpression|System} newUnits 
		 * @type {Quantity}
		 */
		convertTo: function(newUnits) {
			return this.Sr.convert(this, newUnits);
		},
		/**
		 * Returns the UnitExpression associated with this Quantity.
		 * For example, given a Quantity representing "four meters per second",
		 * this method returns a UnitExpression representing "meters per second."
		 * @method
		 * @type {UnitExpression}
		 */
		units: function() {
			return this._unitExpression;
		},
		/**
		 * Returns the scalar value associated with this Quantity.
		 * For example, given a Quantity representing "four meters per second",
		 * this method returns the number 4.
		 * @method
		 * @type {number}
		 */
		value: function() {
			return this._value;
		},
		/**
		 * Adds another Quantity to this Quantity and returns
		 * the result as a new Quantity.
		 * 
		 * If the addend `rhs` is not expressed in the same units as this Quantity,
		 * `rhs` will be converted to the same units as this Quantity.
		 * 
		 * If `rhs` cannot be converted to the same units as this Quantity,
		 * (because, say, the Quantities are not dimensionally compatible)
		 * an Error is thrown.
		 * @method
		 * @param {Quantity} rhs - a dimensionally-compatible Quantity to add to this Quantity.
		 * @type {Quantity}
		 */
		plus: function(rhs) {
			return ArithmeticOps.add(this, rhs);
		},
		/**
		 * Subtracts another Quantity from this Quantity and returns
		 * the result as a new Quantity.
		 * 
		 * If the subtractend `rhs` is not expressed in the same units as this Quantity,
		 * `rhs` will be converted to the same units as this Quantity.
		 * 
		 * If `rhs` cannot be converted to the same units as this Quantity,
		 * (because, say, the Quantities are not dimensionally compatible)
		 * an Error is thrown.
		 * @method
		 * @param {Quantity} rhs - a dimensionally-compatible Quantity to subtract from this Quantity.
		 * @type {Quantity}
		 */
		minus: function(rhs) {
			return ArithmeticOps.subtract(this, rhs);
		},
		/**
		 * Multiplies this Quantity by another Quantity and returns
		 * the result as a new Quantity.
		 * 
		 * No unit conversion is performed on the multiplicand `rhs`.
		 * @method
		 * @param {Quantity} rhs - the Quantity by which to multiply this Quantity.  
		 * @type {Quantity}
		 */
		times: function(rhs) {
			return ArithmeticOps.multiply(this, rhs);
		},
		/**
		 * Divides this Quantity by another Quantity and returns
		 * the result as a new Quantity.
		 * 
		 * No unit conversion is performed on the divisor `rhs`.
		 * @method
		 * @param {Quantity} rhs - the Quantity by which to divide this Quantity.  
		 * @type {Quantity}
		 */
		dividedBy: function(rhs) {
			return ArithmeticOps.divide(this, rhs);
		},
		/**
		 * Returns a string representation of this Quantity,
		 * including the `value()` and the symbolic representation 
		 * of `expression()`.
		 * 
		 * For instance, a Quantity representing "four meters"
		 * will be rendered "4m".
		 * @method
		 * @type {string}
		 */
		toString: function toString() {
			return this.value() + this.units().toString();
		}
	};

	Object.defineProperty(Quantity.prototype, 'constructor', {
		value: Quantity,
		enumerable: false,
		writable: true
	});

	function quantity(value, unitExpression, Sr) {
		return new Quantity(unitExpression, value, Sr);
	}

	return Quantity;
});