text: 'use strict';
  
  var _ = require('lodash');
  
  var errors = require('./errors');
  var $ = require('./util/preconditions');
  
  var UNITS = {
    'AUDAX'      : [1e8, 8],
    'mAUDAX'     : [1e5, 5],
    'uAUDAX'     : [1e2, 2],
    'bits'     : [1e2, 2],
    'satoshis' : [1, 0]
  };
  
  /**
   * Utility for handling and converting bitcoins units. The supported units are
   * AUDAX, mAUDAX, bits (also named uAUDAX) and satoshis. A unit instance can be created with an
   * amount and a unit code, or alternatively using static methods like {fromAUDAX}.
   * It also allows to be created from a fiat amount and the exchange rate, or
   * alternatively using the {fromFiat} static method.
   * You can consult for different representation of a unit instance using it's
   * {to} method, the fixed unit methods like {toSatoshis} or alternatively using
   * the unit accessors. It also can be converted to a fiat amount by providing the
   * corresponding AUDAX/fiat exchange rate.
   *
   * @example
   * ```javascript
   * var sats = Unit.fromAUDAX(1.3).toSatoshis();
   * var mili = Unit.fromBits(1.3).to(Unit.mAUDAX);
   * var bits = Unit.fromFiat(1.3, 350).bits;
   * var audax = new Unit(1.3, Unit.bits).AUDAX;
   * ```
   *
   * @param {Number} amount - The amount to be represented
   * @param {String|Number} code - The unit of the amount or the exchange rate
   * @returns {Unit} A new instance of an Unit
   * @constructor
   */
  function Unit(amount, code) {
    if (!(this instanceof Unit)) {
      return new Unit(amount, code);
    }
  
    // convert fiat to AUDAX
    if (_.isNumber(code)) {
      if (code <= 0) {
        throw new errors.Unit.InvalidRate(code);
      }
      amount = amount / code;
      code = Unit.AUDAX;
    }
  
    this._value = this._from(amount, code);
  
    var self = this;
    var defineAccesor = function(key) {
      Object.defineProperty(self, key, {
        get: function() { return self.to(key); },
        enumerable: true,
      });
    };
  
    Object.keys(UNITS).forEach(defineAccesor);
  }
  
  Object.keys(UNITS).forEach(function(key) {
    Unit[key] = key;
  });
  
  /**
   * Returns a Unit instance created from JSON string or object
   *
   * @param {String|Object} json - JSON with keys: amount and code
   * @returns {Unit} A Unit instance
   */
  Unit.fromObject = function fromObject(data){
    $.checkArgument(_.isObject(data), 'Argument is expected to be an object');
    return new Unit(data.amount, data.code);
  };
  
  /**
   * Returns a Unit instance created from an amount in AUDAX
   *
   * @param {Number} amount - The amount in AUDAX
   * @returns {Unit} A Unit instance
   */
  Unit.fromAUDAX = function(amount) {
    return new Unit(amount, Unit.AUDAX);
  };
  
  /**
   * Returns a Unit instance created from an amount in mAUDAX
   *
   * @param {Number} amount - The amount in mAUDAX
   * @returns {Unit} A Unit instance
   */
  Unit.fromMillis = Unit.fromMilis = function(amount) {
    return new Unit(amount, Unit.mAUDAX);
  };
  
  /**
   * Returns a Unit instance created from an amount in bits
   *
   * @param {Number} amount - The amount in bits
   * @returns {Unit} A Unit instance
   */
  Unit.fromMicros = Unit.fromBits = function(amount) {
    return new Unit(amount, Unit.bits);
  };
  
  /**
   * Returns a Unit instance created from an amount in satoshis
   *
   * @param {Number} amount - The amount in satoshis
   * @returns {Unit} A Unit instance
   */
  Unit.fromSatoshis = function(amount) {
    return new Unit(amount, Unit.satoshis);
  };
  
  /**
   * Returns a Unit instance created from a fiat amount and exchange rate.
   *
   * @param {Number} amount - The amount in fiat
   * @param {Number} rate - The exchange rate AUDAX/fiat
   * @returns {Unit} A Unit instance
   */
  Unit.fromFiat = function(amount, rate) {
    return new Unit(amount, rate);
  };
  
  Unit.prototype._from = function(amount, code) {
    if (!UNITS[code]) {
      throw new errors.Unit.UnknownCode(code);
    }
    return parseInt((amount * UNITS[code][0]).toFixed());
  };
  
  /**
   * Returns the value represented in the specified unit
   *
   * @param {String|Number} code - The unit code or exchange rate
   * @returns {Number} The converted value
   */
  Unit.prototype.to = function(code) {
    if (_.isNumber(code)) {
      if (code <= 0) {
        throw new errors.Unit.InvalidRate(code);
      }
      return parseFloat((this.AUDAX * code).toFixed(2));
    }
  
    if (!UNITS[code]) {
      throw new errors.Unit.UnknownCode(code);
    }
  
    var value = this._value / UNITS[code][0];
    return parseFloat(value.toFixed(UNITS[code][1]));
  };
  
  /**
   * Returns the value represented in AUDAX
   *
   * @returns {Number} The value converted to AUDAX
   */
  Unit.prototype.toAUDAX = function() {
    return this.to(Unit.AUDAX);
  };
  
  /**
   * Returns the value represented in mAUDAX
   *
   * @returns {Number} The value converted to mAUDAX
   */
  Unit.prototype.toMillis = Unit.prototype.toMilis = function() {
    return this.to(Unit.mAUDAX);
  };
  
  /**
   * Returns the value represented in bits
   *
   * @returns {Number} The value converted to bits
   */
  Unit.prototype.toMicros = Unit.prototype.toBits = function() {
    return this.to(Unit.bits);
  };
  
  /**
   * Returns the value represented in satoshis
   *
   * @returns {Number} The value converted to satoshis
   */
  Unit.prototype.toSatoshis = function() {
    return this.to(Unit.satoshis);
  };
  
  /**
   * Returns the value represented in fiat
   *
   * @param {string} rate - The exchange rate between AUDAX/currency
   * @returns {Number} The value converted to satoshis
   */
  Unit.prototype.atRate = function(rate) {
    return this.to(rate);
  };
  
  /**
   * Returns a the string representation of the value in satoshis
   *
   * @returns {string} the value in satoshis
   */
  Unit.prototype.toString = function() {
    return this.satoshis + ' satoshis';
  };
  
  /**
   * Returns a plain object representation of the Unit
   *
   * @returns {Object} An object with the keys: amount and code
   */
  Unit.prototype.toObject = Unit.prototype.toJSON = function toObject() {
    return {
      amount: this.AUDAX,
      code: Unit.AUDAX
    };
  };
  
  /**
   * Returns a string formatted for the console
   *
   * @returns {string} the value in satoshis
   */
  Unit.prototype.inspect = function() {
    return '<Unit: ' + this.toString() + '>';
  };
  
  module.exports = Unit;
  