/*
 * Id is an object representation of a peer Id. a peer Id is a multihash
 */

'use strict';

var fs = require('fs');
var multihashing = require('multihashing');
var base58 = require('bs58');
var forge = require('node-forge');
var protobuf = require('protocol-buffers');
var path = require('path');

var pbCrypto = protobuf(fs.readFileSync(path.resolve(__dirname, '../protos/crypto.proto')));

exports = module.exports = PeerId;

exports.Buffer = Buffer;

function PeerId(id, privKey, pubKey) {
  var self = this;

  if (!(self instanceof PeerId)) {
    throw new Error('Id must be called with new');
  }

  self.privKey = privKey;
  self.pubKey = pubKey;
  self.id = id; // multihash - sha256 - buffer

  // pretty print
  self.toPrint = function () {
    return {
      id: self.toB58String(),
      privKey: privKey.toString('hex'),
      pubKey: pubKey.toString('hex')
    };
  };

  self.toJSON = function () {
    return {
      id: self.id.toString('hex'),
      privKey: self.privKey.toString('hex'),
      pubKey: self.pubKey.toString('hex')
    };
  };

  // encode/decode functions
  self.toHexString = function () {
    return self.id.toString('hex');
  };

  self.toBytes = function () {
    return self.id;
  };

  self.toB58String = function () {
    return base58.encode(self.id);
  };
}

// unwrap the private key protobuf
function keyUnmarshal(key) {
  return pbCrypto.PrivateKey.decode(key);
}

// create a public key protobuf to be base64 string stored in config
function keyMarshal(data, type) {
  var RSA = 0;

  var epb = void 0;
  if (type === 'Public') {
    epb = pbCrypto.PublicKey.encode({
      Type: RSA,
      Data: data
    });
  }

  if (type === 'Private') {
    epb = pbCrypto.PrivateKey.encode({
      Type: RSA,
      Data: data
    });
  }

  return epb;
}

// this returns a base64 encoded protobuf of the public key
function formatKey(key, type) {
  // create der buffer of public key asn.1 object
  var der = forge.asn1.toDer(key);

  // create forge buffer of der public key buffer
  var fDerBuf = forge.util.createBuffer(der.data, 'binary');

  // convert forge buffer to node buffer public key
  var nDerBuf = new Buffer(fDerBuf.getBytes(), 'binary');

  // protobuf the new DER bytes to the PublicKey Data: field
  var marsheledKey = keyMarshal(nDerBuf, type);

  // encode the protobuf public key to base64 string
  var b64 = marsheledKey.toString('base64');
  return b64;
}

// generation
exports.create = function (opts) {
  opts = opts || {};
  opts.bits = opts.bits || 2048;

  // generate keys
  var pair = forge.rsa.generateKeyPair({
    bits: opts.bits,
    e: 0x10001
  });

  // return the RSA public/private key to asn1 object
  var asnPub = forge.pki.publicKeyToAsn1(pair.publicKey);
  var asnPriv = forge.pki.privateKeyToAsn1(pair.privateKey);

  // format the keys to protobuf base64 encoded string
  var protoPublic64 = formatKey(asnPub, 'Public');
  var protoPrivate64 = formatKey(asnPriv, 'Private');

  // store the keys as a buffer
  var bufProtoPub64 = new Buffer(protoPublic64, 'base64');
  var bufProtoPriv64 = new Buffer(protoPrivate64, 'base64');

  var mhId = multihashing(new Buffer(protoPublic64, 'base64'), 'sha2-256');

  return new PeerId(mhId, bufProtoPriv64, bufProtoPub64);
};

exports.createFromHexString = function (str) {
  return new PeerId(new Buffer(str, 'hex'));
};

exports.createFromBytes = function (buf) {
  return new PeerId(buf);
};

exports.createFromB58String = function (str) {
  return new PeerId(new Buffer(base58.decode(str)));
};

// Public Key input will be a buffer
exports.createFromPubKey = function (pubKey) {
  var buf = new Buffer(pubKey, 'base64');
  var mhId = multihashing(buf, 'sha2-256');
  return new PeerId(mhId, null, pubKey);
};

// Private key input will be a string
exports.createFromPrivKey = function (privKey) {
  // create a buffer from the base64 encoded string
  var buf = new Buffer(privKey, 'base64');

  // get the private key data from the protobuf
  var mpk = keyUnmarshal(buf);

  // create a forge buffer
  var fbuf = forge.util.createBuffer(mpk.Data.toString('binary'));

  // create an asn1 object from the private key bytes saved in the protobuf Data: field
  var asnPriv = forge.asn1.fromDer(fbuf);

  // get the RSA privatekey data from the asn1 object
  var privateKey = forge.pki.privateKeyFromAsn1(asnPriv);

  // set the RSA public key to the modulus and exponent of the private key
  var publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);

  // return the RSA public key to asn1 object
  var asnPub = forge.pki.publicKeyToAsn1(publicKey);

  // format the public key
  var protoPublic64 = formatKey(asnPub, 'Public');

  // buffer the public key for consistency before storing
  var bufProtoPub64 = new Buffer(protoPublic64, 'base64');
  var mhId = multihashing(new Buffer(protoPublic64, 'base64'), 'sha2-256');
  return new PeerId(mhId, privKey, bufProtoPub64);
};

exports.createFromJSON = function (obj) {
  return new PeerId(new Buffer(obj.id, 'hex'), new Buffer(obj.privKey, 'hex'), new Buffer(obj.pubKey, 'hex'));
};