//
// Seed support
//

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

var Base    = require('./base').Base;
var UInt    = require('./uint').UInt;
var UInt256 = require('./uint256').UInt256;
var UInt160 = require('./uint160').UInt160;
var KeyPair = require('./keypair').KeyPair;

var Seed = extend(function () {
  this._curve = sjcl.ecc.curves.k256;
  this._value = NaN;
}, UInt);

Seed.width = 16;
Seed.prototype = extend({}, UInt.prototype);
Seed.prototype.constructor = Seed;

// value = NaN on error.
// One day this will support rfc1751 too.
Seed.prototype.parse_json = function (j) {
  if (typeof j === 'string') {
    if (!j.length) {
      this._value = NaN;
    // XXX Should actually always try and continue if it failed.
    } else if (j[0] === 's') {
      this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
    } else if (/^[0-9a-fA-f]{32}$/.test(j)) {
      this.parse_hex(j);
    // XXX Should also try 1751
    } else {
      this.parse_passphrase(j);
    }
  } else {
    this._value = NaN;
  }

  return this;
};

Seed.prototype.parse_passphrase = function (j) {
  if (typeof j !== 'string') {
    throw new Error('Passphrase must be a string');
  }

  var hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j));
  var bits = sjcl.bitArray.bitSlice(hash, 0, 128);

  this.parse_bits(bits);

  return this;
};

Seed.prototype.to_json = function () {
  if (!(this.is_valid())) {
    return NaN;
  }

  var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());

  return output;
};

function append_int(a, i) {
  return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
};

function firstHalfOfSHA512(bytes) {
  return sjcl.bitArray.bitSlice(
    sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
    0, 256
  );
};

function SHA256_RIPEMD160(bits) {
  return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
};

/**
* @param account
*        {undefined}                 take first, default, KeyPair
*
*        {Number}                    specifies the account number of the KeyPair
*                                    desired.
*
*        {Uint160} (from_json able), specifies the address matching the KeyPair
*                                    that is desired.
*
* @param maxLoops (optional)
*        {Number}                    specifies the amount of attempts taken to generate
*                                    a matching KeyPair
*/
Seed.prototype.get_key = function (account, maxLoops) {
  var account_number = 0, address;
  var max_loops = maxLoops || 1;

  if (!this.is_valid()) {
    throw new Error('Cannot generate keys from invalid seed!');
  }
  if (account) {
    if (typeof account === 'number') {
      account_number = account;
      max_loops = account_number+1;
    } else {
      address = UInt160.from_json(account);
    }
  }

  var private_gen, public_gen;
  var curve = this._curve;
  var i = 0;

  do {
    private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i)));
    i++;
  } while (!curve.r.greaterEquals(private_gen));

  public_gen = curve.G.mult(private_gen);

  var sec;
  var key_pair;

  do {

    i = 0;

    do {
      sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), account_number), i)));
      i++;
    } while (!curve.r.greaterEquals(sec));

    account_number++;
    sec = sec.add(private_gen).mod(curve.r);
    key_pair = KeyPair.from_bn_secret(sec);

    if (max_loops-- <= 0) {
      // We are almost certainly looking for an account that would take same
      // value of $too_long {forever, ...}
      throw new Error('Too many loops looking for KeyPair yielding '+
                      address.to_json() +' from ' + this.to_json());
    }

  } while (address && !key_pair.get_address().equals(address));
    return key_pair;
};

exports.Seed = Seed;
