Source: UnitzClass.js


/**
 * Instantiates a new class instance with a name. A class stores information on
 * how to convert values between units, the set of valid strings to denote a
 * unit group, the singular and plural versions of a unit, and the valid
 * fraction denominators for that unit.
 *
 * @memberof Unitz
 * @alias Class
 * @class
 * @constructor
 * @param {String} className -
 *    The name of the class.
 */
function UnitzClass(className)
{
  /**
   * The name of the class.
   *
   * @member {String}
   */
  this.className = className;

  /**
   * The map of numbers it takes to get from one unit to another.
   *
   * @member {Object}
   */
  this.converters = {};

  /**
   * The map of numbers it takes to get from one base unit to another base unit.
   * There can be multiple systems in a single class (like Imperial vs Metric)
   * and when conversion takes place in {@link Unitz.convert} from one system
   * to another this map is used.
   *
   * @member {Object}
   */
  this.mapping = {};

  /**
   * A map of unit aliases to their base unit.
   *
   * @member {Object}
   */
  this.bases = {};

  /**
   * The array of groups in this class.
   *
   * @member {Unitz.Group[]}
   */
  this.groups = [];

  /**
   * The map of unit aliases to their group.
   *
   * @member {Object}
   */
  this.groupMap = {};
}

UnitzClass.prototype =
{

  /**
   * Adds a unit group to this class. A unit group is a unit relative to another
   * unit and has it's own aliases for the unit, singluar & plural
   * representations, and denominators to make unit-friendly fractions. The
   * group added to this class is returned.
   *
   * @method
   * @memberof Unitz.Class#
   * @param {Number} relativeValue -
   *    The value to scale by to get from the `relativeTo` unit to this unit
   *    group. If this is a base unit then this value should be 1 and the
   *    `relativeTo` should be not given.
   * @param {String} relativeTo -
   *    The unit the unit group being added is relative to. This should not be
   *    given when defining a base unit.
   * @param {String[]} units -
   *    The aliases for the unit group being added, each are valid ways to
   *    represent this group. These MUST be in lowercase form.
   * @param {Number[]} denominators -
   *    The denominators that are valid for the group being added. This is used
   *    so you don't see odd fractions that don't make sense for the given unit.
   * @param {String} singular -
   *    The singular unit (when a |value| is 1) to use for the added group.
   * @param {String} plural -
   *    The plural unit (when a |value| is not 1) to use for the added group.
   * @return {Unitz.Group} -
   *    The unit group added to this class.
   */
  addGroup: function(relativeValue, relativeTo, units, denominators, singular, plural)
  {
    var mainUnit = units[ 0 ];
    var baseUnit = mainUnit;

    if ( relativeTo )
    {
      relativeValue *= this.converters[ relativeTo ];

      baseUnit = this.bases[ relativeTo ];
    }

    var group = new UnitzGroup( mainUnit, baseUnit, relativeValue, units, singular, plural, denominators );

    for (var i = 0; i < units.length; i++)
    {
      var unit = units[ i ];

      this.converters[ unit ] = relativeValue;
      this.bases[ unit ] = baseUnit;
      this.groupMap[ unit ] = group;
    }

    this.groups.push( group );

    return group;
  },

  /**
   * Removes a unit from this class. The group the unit to still exists in this
   * class, but the unit won't be parsed to the group anymore.
   *
   * @method
   * @memberof Unitz.Class#
   * @param {String} unit -
   *    The lowercase unit to remove from this class.
   * @return {Boolean} -
   *    True if the unit was removed, false if it does not exist in this class.
   */
  removeUnit: function(unit)
  {
    var exists = unit in this.converters;

    delete this.converters[ unit ];
    delete this.bases[ unit ];
    delete this.groupMap[ unit ];
    delete unitToClass[ unit ];

    return exists;
  },

  /**
   * Removes the group which has the given unit. The group will be removed
   * entirely from the system and can no longer be parsed or converted to and
   * from.
   *
   * @method
   * @memberof Unitz.Class#
   * @param {String} unit -
   *    The lowercase unit of the group to remove.
   * @return {Boolean} -
   *    True if the group was removed, false if it does not exist in this class.
   */
  removeGroup: function(unit)
  {
    var group = this.groupMap[ unit ];
    var removed = false;

    if ( group )
    {
      var units = group.units;

      for (var i = 0; i < units.length; i++)
      {
        var unit = units[ i ];

        if ( this.groupMap[ unit ] === group )
        {
          delete this.converters[ unit ];
          delete this.bases[ unit ];
          delete this.groupMap[ unit ];
          delete unitToClass[ unit ];
        }
      }

      var index = this.groups.indexOf( group );

      if ( index !== -1 )
      {
        this.groups.splice( index, 1 );

        removed = true;
      }
    }

    return removed;
  },

  /**
   * Adds a one direction conversion from one base unit to another.
   *
   * @method
   * @memberof Unitz.Class#
   * @param {String} source -
   *    The source unit.
   * @param {String} target -
   *    The target unit.
   * @param {Number} value -
   *    The value the source number needs to be multiplied by to get to the
   *    target value.
   * @see Unitz.Class#addBaseConversion
   */
  addOneBaseConversion: function(source, target, value)
  {
    if ( !(source in this.mapping) )
    {
      this.mapping[ source ] = {};
    }

    this.mapping[ source ][ target ] = value;
  },

  /**
   * Adds a bi-directional conversion from one base unit to another.
   *
   * ```javascript
   * uc.addBaseConversion( 'in', 'cm', 2.54 ); // 1 in to 2.54 cm
   * ```
   *
   * @method
   * @memberof Unitz.Class#
   * @param {String} source -
   *    The source unit.
   * @param {String} target -
   *    The target unit.
   * @param {Number} value -
   *    The value the source number needs to be multiplied by to get to the
   *    target value.
   * @see Unitz.Class#addBaseConversion
   */
  addBaseConversion: function(source, target, value)
  {
    this.addOneBaseConversion( source, target, value );
    this.addOneBaseConversion( target, source, 1.0 / value );
  }

};