var utils   = require('./utils');
var sjcl    = utils.sjcl;
var config  = require('./config');

//
// Abstract UInt class
//
// Base class for UInt classes
//

var UInt = function() {
  // Internal form: NaN or sjcl.bn
  this._value  = NaN;
  this._update();
};

UInt.json_rewrite = function(j, opts) {
  return this.from_json(j).to_json(opts);
};

// Return a new UInt from j.
UInt.from_generic = function(j) {
  if (j instanceof this) {
    return j.clone();
  } else {
    return (new this()).parse_generic(j);
  }
};

// Return a new UInt from j.
UInt.from_hex = function(j) {
  if (j instanceof this) {
    return j.clone();
  } else {
    return (new this()).parse_hex(j);
  }
};

// Return a new UInt from j.
UInt.from_json = function(j) {
  if (j instanceof this) {
    return j.clone();
  } else {
    return (new this()).parse_json(j);
  }
};

// Return a new UInt from j.
UInt.from_bits = function(j) {
  if (j instanceof this) {
    return j.clone();
  } else {
    return (new this()).parse_bits(j);
  }
};

// Return a new UInt from j.
UInt.from_bytes = function(j) {
  if (j instanceof this) {
    return j.clone();
  } else {
    return (new this()).parse_bytes(j);
  }
};

// Return a new UInt from j.
UInt.from_bn = function(j) {
  if (j instanceof this) {
    return j.clone();
  } else {
    return (new this()).parse_bn(j);
  }
};

// Return a new UInt from j.
UInt.from_number = function(j) {
  if (j instanceof this) {
    return j.clone();
  } else {
    return (new this()).parse_number(j);
  }
};

UInt.is_valid = function(j) {
  return this.from_json(j).is_valid();
};

UInt.prototype.clone = function() {
  return this.copyTo(new this.constructor());
};

// Returns copy.
UInt.prototype.copyTo = function(d) {
  d._value = this._value;

  if (this._version_byte !== void(0)) {
    d._version_byte = this._version_byte;
  }

  if (typeof d._update === 'function') {
    d._update();
  }

  return d;
};

UInt.prototype.equals = function(d) {
  return this.is_valid() && d.is_valid() && this._value.equals(d._value);
};

UInt.prototype.is_valid = function() {
  return this._value instanceof sjcl.bn;
};

UInt.prototype.is_zero = function() {
  return this.is_valid() && this._value.equals(new sjcl.bn(0));
};

/**
 * Update any derivative values.
 *
 * This allows subclasses to maintain caches of any data that they derive from
 * the main _value. For example, the Currency class keeps the currency type, the
 * currency code and other information about the currency cached.
 *
 * The reason for keeping this mechanism in this class is so every subclass can
 * call it whenever it modifies the internal state.
 */
UInt.prototype._update = function() {
  // Nothing to do by default. Subclasses will override this.
};

// value = NaN on error.
UInt.prototype.parse_generic = function(j) {
  // Canonicalize and validate
  if (config.accounts && (j in config.accounts)) {
    j = config.accounts[j].account;
  }

  switch (j) {
    case undefined:
      case '0':
      case this.constructor.STR_ZERO:
      case this.constructor.ACCOUNT_ZERO:
      case this.constructor.HEX_ZERO:
      this._value  = new sjcl.bn(0);
      break;

    case '1':
      case this.constructor.STR_ONE:
      case this.constructor.ACCOUNT_ONE:
      case this.constructor.HEX_ONE:
      this._value  = new sjcl.bn(1);
      break;

    default:
        if (typeof j !== 'string') {
          this._value  = NaN;
        } else if (this.constructor.width === j.length) {
          var hex = utils.arrayToHex(utils.stringToArray(j));
          this._value  = new sjcl.bn(hex, 16);
        } else if ((this.constructor.width * 2) === j.length) {
          // XXX Check char set!
          this._value  = new sjcl.bn(j, 16);
        } else {
          this._value  = NaN;
        }
  }

  this._update();

  return this;
};

UInt.prototype.parse_hex = function(j) {
  if (typeof j === 'string' && j.length === (this.constructor.width * 2)) {
    this._value = new sjcl.bn(j, 16);
  } else {
    this._value = NaN;
  }

  this._update();

  return this;
};

UInt.prototype.parse_bits = function(j) {
  if (sjcl.bitArray.bitLength(j) !== this.constructor.width * 8) {
    this._value = NaN;
  } else {
    this._value = sjcl.bn.fromBits(j);
    // var bytes = sjcl.codec.bytes.fromBits(j);
    // this.parse_bytes(bytes);
  }

  this._update();

  return this;
};


UInt.prototype.parse_bytes = function(j) {
  if (!Array.isArray(j) || j.length !== this.constructor.width) {
    this._value = NaN;
  } else {
    var bits = sjcl.codec.bytes.toBits(j);
    this._value  = sjcl.bn.fromBits(bits);
  }

  this._update();

  return this;
};


UInt.prototype.parse_json = UInt.prototype.parse_hex;

UInt.prototype.parse_bn = function(j) {
  if ((j instanceof sjcl.bn) && j.bitLength() <= this.constructor.width * 8) {
    // var bytes = sjcl.codec.bytes.fromBits(j.toBits());
    // this._value  = new sjcl.bn(utils.arrayToHex(bytes), 16);
    this._value = new sjcl.bn(j);
  } else {
    this._value = NaN;
  }

  this._update();

  return this;
};

UInt.prototype.parse_number = function(j) {
  this._value = NaN;

  if (typeof j === 'number' && isFinite(j) && j >= 0) {
    this._value = new sjcl.bn(j);
  }

  this._update();

  return this;
};

// Convert from internal form.
UInt.prototype.to_bytes = function() {
  if (!this.is_valid()) {
    return null;
  }
  return sjcl.codec.bytes.fromBits(this.to_bits());
};

UInt.prototype.to_hex = function() {
  if (!this.is_valid()) {
    return null;
  }
  return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
};

UInt.prototype.to_json = UInt.prototype.to_hex;

UInt.prototype.to_bits = function() {
  if (!this.is_valid()) {
    return null;
  }

  return this._value.toBits(this.constructor.width * 8);
};

UInt.prototype.to_bn = function() {
  if (!this.is_valid()) {
    return null;
  }

  var bits = this.to_bits();

  return sjcl.bn.fromBits(bits);
};

exports.UInt = UInt;

// vim:sw=2:sts=2:ts=8:et
