'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.BlockstackNamespace = undefined;

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

exports.makePreorderSkeleton = makePreorderSkeleton;
exports.makeRegisterSkeleton = makeRegisterSkeleton;
exports.makeRenewalSkeleton = makeRenewalSkeleton;
exports.makeTransferSkeleton = makeTransferSkeleton;
exports.makeUpdateSkeleton = makeUpdateSkeleton;
exports.makeRevokeSkeleton = makeRevokeSkeleton;
exports.makeNamespacePreorderSkeleton = makeNamespacePreorderSkeleton;
exports.makeNamespaceRevealSkeleton = makeNamespaceRevealSkeleton;
exports.makeNamespaceReadySkeleton = makeNamespaceReadySkeleton;
exports.makeNameImportSkeleton = makeNameImportSkeleton;
exports.makeAnnounceSkeleton = makeAnnounceSkeleton;

var _bitcoinjsLib = require('bitcoinjs-lib');

var _bitcoinjsLib2 = _interopRequireDefault(_bitcoinjsLib);

var _bigi = require('bigi');

var _bigi2 = _interopRequireDefault(_bigi);

var _utils = require('./utils');

var _config = require('../config');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

// todo : add name length / character verification

// support v1 and v2 price API endpoint return values
var BlockstackNamespace = exports.BlockstackNamespace = function () {
  function BlockstackNamespace(namespaceID) {
    _classCallCheck(this, BlockstackNamespace);

    if (namespaceID.length > 19) {
      throw new Error('Namespace ID too long (19 chars max)');
    }
    if (!namespaceID.match('[0123456789abcdefghijklmnopqrstuvwxyz_-]+')) {
      throw new Error('Namespace ID can only use characters 0123456789abcdefghijklmnopqrstuvwxyz-_');
    }

    this.namespaceID = namespaceID;
    this.version = -1;
    this.lifetime = -1;
    this.coeff = -1;
    this.base = -1;
    this.buckets = [-1];
    this.nonalphaDiscount = -1;
    this.noVowelDiscount = -1;
  }

  _createClass(BlockstackNamespace, [{
    key: 'check',
    value: function check() {
      try {
        this.setVersion(this.version);
        this.setLifetime(this.lifetime);
        this.setCoeff(this.coeff);
        this.setBase(this.base);
        this.setBuckets(this.buckets);
        this.setNonalphaDiscount(this.nonalphaDiscount);
        this.setNoVowelDiscount(this.noVowelDiscount);
        return true;
      } catch (e) {
        return false;
      }
    }
  }, {
    key: 'setVersion',
    value: function setVersion(version) {
      if (version < 0 || version > Math.pow(2, 16) - 1) {
        throw new Error('Invalid version: must be a 16-bit number');
      }
      this.version = version;
    }
  }, {
    key: 'setLifetime',
    value: function setLifetime(lifetime) {
      if (lifetime < 0 || lifetime > Math.pow(2, 32) - 1) {
        throw new Error('Invalid lifetime: must be a 32-bit number');
      }
      this.lifetime = lifetime;
    }
  }, {
    key: 'setCoeff',
    value: function setCoeff(coeff) {
      if (coeff < 0 || coeff > 255) {
        throw new Error('Invalid coeff: must be an 8-bit number');
      }
      this.coeff = coeff;
    }
  }, {
    key: 'setBase',
    value: function setBase(base) {
      if (base < 0 || base > 255) {
        throw new Error('Invalid base: must be an 8-bit number');
      }
      this.base = base;
    }
  }, {
    key: 'setBuckets',
    value: function setBuckets(buckets) {
      if (buckets.length !== 16) {
        throw new Error('Invalid buckets: must have 16 entries');
      }

      for (var i = 0; i < buckets.length; i++) {
        if (buckets[i] < 0 || buckets[i] > 15) {
          throw new Error('Invalid buckets: must be 4-bit numbers');
        }
      }

      this.buckets = buckets.slice(0);
    }
  }, {
    key: 'setNonalphaDiscount',
    value: function setNonalphaDiscount(nonalphaDiscount) {
      if (nonalphaDiscount < 0 || nonalphaDiscount > 15) {
        throw new Error('Invalid nonalphaDiscount: must be a 4-bit number');
      }
      this.nonalphaDiscount = nonalphaDiscount;
    }
  }, {
    key: 'setNoVowelDiscount',
    value: function setNoVowelDiscount(noVowelDiscount) {
      if (noVowelDiscount < 0 || noVowelDiscount > 15) {
        throw new Error('Invalid noVowelDiscount: must be a 4-bit number');
      }
      this.noVowelDiscount = noVowelDiscount;
    }
  }, {
    key: 'toHexPayload',
    value: function toHexPayload() {
      var lifeHex = ('00000000' + this.lifetime.toString(16)).slice(-8);
      var coeffHex = ('00' + this.coeff.toString(16)).slice(-2);
      var baseHex = ('00' + this.base.toString(16)).slice(-2);
      var bucketHex = this.buckets.map(function (b) {
        return b.toString(16);
      }).reduce(function (b1, b2) {
        return b1 + b2;
      }, '');
      var discountHex = this.nonalphaDiscount.toString(16) + this.noVowelDiscount.toString(16);
      var versionHex = ('0000' + this.version.toString(16)).slice(-4);
      var namespaceIDHex = new Buffer(this.namespaceID).toString('hex');

      return lifeHex + coeffHex + baseHex + bucketHex + discountHex + versionHex + namespaceIDHex;
    }
  }]);

  return BlockstackNamespace;
}();

function asAmountV2(amount) {
  // convert an AmountType v1 or v2 to an AmountTypeV2.
  // the "units" of a v1 amount type are always 'BTC'
  if (typeof amount === 'number') {
    return { units: 'BTC', amount: _bigi2.default.fromByteArrayUnsigned(String(amount)) };
  } else {
    return { units: amount.units, amount: amount.amount };
  }
}

function makeTXbuilder() {
  var txb = new _bitcoinjsLib2.default.TransactionBuilder(_config.config.network.layer1);
  txb.setVersion(1);
  return txb;
}

function makePreorderSkeleton(fullyQualifiedName, consensusHash, preorderAddress, burnAddress, burn) {
  var registerAddress = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;

  // Returns a preorder tx skeleton.
  //   with 3 outputs : 1. the Blockstack Preorder OP_RETURN data
  //                    2. the Preorder's change address (5500 satoshi minimum)
  //                    3. the BURN
  //
  // 0     2  3                                     23             39
  // |-----|--|--------------------------------------|--------------|
  // magic op  hash160(fqn,scriptPubkey,registerAddr) consensus hash
  //
  // Returns an unsigned serialized transaction.
  var burnAmount = asAmountV2(burn);
  var network = _config.config.network;
  var nameBuff = Buffer.from((0, _utils.decodeB40)(fullyQualifiedName), 'hex'); // base40
  var scriptPublicKey = _bitcoinjsLib2.default.address.toOutputScript(preorderAddress, network.layer1);

  var dataBuffers = [nameBuff, scriptPublicKey];

  if (!!registerAddress) {
    var registerBuff = Buffer.from(registerAddress, 'ascii');
    dataBuffers.push(registerBuff);
  }

  var dataBuff = Buffer.concat(dataBuffers);

  var hashed = (0, _utils.hash160)(dataBuff);

  var opReturnBufferLen = 39;
  var opReturnBuffer = Buffer.alloc(opReturnBufferLen);
  opReturnBuffer.write('id?', 0, 3, 'ascii');
  hashed.copy(opReturnBuffer, 3);
  opReturnBuffer.write(consensusHash, 23, 16, 'hex');

  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;

  var tx = makeTXbuilder();

  tx.addOutput(nullOutput, 0);
  tx.addOutput(preorderAddress, _utils.DUST_MINIMUM);

  var btcBurnAmount = parseInt(burnAmount.amount.toHex(), 16);
  tx.addOutput(burnAddress, btcBurnAmount);

  return tx.buildIncomplete();
}

function makeRegisterSkeleton(fullyQualifiedName, ownerAddress) {
  var valueHash = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;

  // Returns a register tx skeleton.
  //   with 2 outputs : 1. The register OP_RETURN
  //                    2. The owner address (can be different from REGISTER address on renewals)

  // You MUST make the first input a UTXO from the current OWNER *or* the
  //   funder of the PREORDER

  // in the case of a renewal, this would need to be modified to include a change address
  //  as output (3) before the burn output (4)

  /*
    Formats
     No zonefile hash, and pay with BTC:
     0    2  3                                  39
    |----|--|----------------------------------|
    magic op   name.ns_id (up to 37 bytes)
      With zonefile hash, and pay with BTC:
     0    2  3                                  39                  59
    |----|--|----------------------------------|-------------------|
    magic op   name.ns_id (37 bytes, 0-padded)     zone file hash
   */

  var payload = void 0;
  if (!!valueHash) {
    if (valueHash.length !== 40) {
      throw new Error('Value hash length incorrect. Expecting 20-bytes, hex-encoded');
    }

    var payloadLen = 57;
    payload = Buffer.alloc(payloadLen, 0);
    payload.write(fullyQualifiedName, 0, 37, 'ascii');
    payload.write(valueHash, 37, 20, 'hex');
  } else {
    payload = Buffer.from(fullyQualifiedName, 'ascii');
  }

  var opReturnBuffer = Buffer.concat([Buffer.from('id:', 'ascii'), payload]);
  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;

  var tx = makeTXbuilder();

  tx.addOutput(nullOutput, 0);
  tx.addOutput(ownerAddress, _utils.DUST_MINIMUM);

  return tx.buildIncomplete();
}

function makeRenewalSkeleton(fullyQualifiedName, nextOwnerAddress, lastOwnerAddress, burnAddress, burn) {
  var valueHash = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;

  /*
    Formats
     No zonefile hash, and pay with BTC:
     0    2  3                                  39
    |----|--|----------------------------------|
    magic op   name.ns_id (up to 37 bytes)
      With zonefile hash, and pay with BTC:
     0    2  3                                  39                  59
    |----|--|----------------------------------|-------------------|
    magic op   name.ns_id (37 bytes, 0-padded)     zone file hash
   */
  var burnAmount = asAmountV2(burn);
  var network = _config.config.network;
  var burnBTCAmount = parseInt(burnAmount.amount.toHex(), 16);

  var registerTX = makeRegisterSkeleton(fullyQualifiedName, nextOwnerAddress, valueHash);
  var txB = _bitcoinjsLib2.default.TransactionBuilder.fromTransaction(registerTX, network.layer1);
  txB.addOutput(lastOwnerAddress, _utils.DUST_MINIMUM);
  txB.addOutput(burnAddress, burnBTCAmount);
  return txB.buildIncomplete();
}

function makeTransferSkeleton(fullyQualifiedName, consensusHash, newOwner) {
  var keepZonefile = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;

  // Returns a transfer tx skeleton.
  //   with 2 outputs : 1. the Blockstack Transfer OP_RETURN data
  //                    2. the new owner with a DUST_MINIMUM value (5500 satoshi)
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction.
  /*
    Format
     0     2  3    4                   20              36
    |-----|--|----|-------------------|---------------|
    magic op keep  hash128(name.ns_id) consensus hash
             data?
  */
  var opRet = Buffer.alloc(36);
  var keepChar = '~';
  if (keepZonefile) {
    keepChar = '>';
  }

  opRet.write('id>', 0, 3, 'ascii');
  opRet.write(keepChar, 3, 1, 'ascii');

  var hashed = (0, _utils.hash128)(Buffer.from(fullyQualifiedName, 'ascii'));
  hashed.copy(opRet, 4);
  opRet.write(consensusHash, 20, 16, 'hex');

  var opRetPayload = _bitcoinjsLib2.default.payments.embed({ data: [opRet] }).output;

  var tx = makeTXbuilder();

  tx.addOutput(opRetPayload, 0);
  tx.addOutput(newOwner, _utils.DUST_MINIMUM);

  return tx.buildIncomplete();
}

function makeUpdateSkeleton(fullyQualifiedName, consensusHash, valueHash) {
  // Returns an update tx skeleton.
  //   with 1 output : 1. the Blockstack update OP_RETURN
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction.
  /*
    Format:
     0     2  3                                   19                      39
    |-----|--|-----------------------------------|-----------------------|
    magic op  hash128(name.ns_id,consensus hash) hash160(data)
  */

  var opRet = Buffer.alloc(39);

  var nameBuff = Buffer.from(fullyQualifiedName, 'ascii');
  var consensusBuff = Buffer.from(consensusHash, 'ascii');

  var hashedName = (0, _utils.hash128)(Buffer.concat([nameBuff, consensusBuff]));

  opRet.write('id+', 0, 3, 'ascii');
  hashedName.copy(opRet, 3);
  opRet.write(valueHash, 19, 20, 'hex');

  var opRetPayload = _bitcoinjsLib2.default.payments.embed({ data: [opRet] }).output;

  var tx = makeTXbuilder();

  tx.addOutput(opRetPayload, 0);

  return tx.buildIncomplete();
}

function makeRevokeSkeleton(fullyQualifiedName) {
  // Returns a revoke tx skeleton
  //    with 1 output: 1. the Blockstack revoke OP_RETURN
  //
  // You MUST make the first input a UTXO from the current OWNER
  //
  // Returns an unsigned serialized transaction
  /*
   Format:
    0    2  3                             39
   |----|--|-----------------------------|
   magic op   name.ns_id (37 bytes)
  */

  var opRet = Buffer.alloc(3);

  var nameBuff = Buffer.from(fullyQualifiedName, 'ascii');

  opRet.write('id~', 0, 3, 'ascii');

  var opReturnBuffer = Buffer.concat([opRet, nameBuff]);
  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;

  var tx = makeTXbuilder();

  tx.addOutput(nullOutput, 0);

  return tx.buildIncomplete();
}

function makeNamespacePreorderSkeleton(namespaceID, consensusHash, preorderAddress, registerAddress, burn) {
  // Returns a namespace preorder tx skeleton.
  // Returns an unsigned serialized transaction.
  /*
   Formats:
    0     2   3                                      23               39
   |-----|---|--------------------------------------|----------------|
   magic op  hash(ns_id,script_pubkey,reveal_addr)   consensus hash
  */

  var burnAmount = asAmountV2(burn);
  if (burnAmount.units !== 'BTC') {
    throw new Error('Invalid burnUnits ' + burnAmount.units);
  }

  var network = _config.config.network;
  var burnAddress = network.getDefaultBurnAddress();
  var namespaceIDBuff = Buffer.from((0, _utils.decodeB40)(namespaceID), 'hex'); // base40
  var scriptPublicKey = _bitcoinjsLib2.default.address.toOutputScript(preorderAddress, network.layer1);
  var registerBuff = Buffer.from(registerAddress, 'ascii');

  var dataBuffers = [namespaceIDBuff, scriptPublicKey, registerBuff];
  var dataBuff = Buffer.concat(dataBuffers);

  var hashed = (0, _utils.hash160)(dataBuff);

  var btcBurnAmount = parseInt(burnAmount.amount.toHex(), 16);
  var opReturnBufferLen = 39;

  var opReturnBuffer = Buffer.alloc(opReturnBufferLen);
  opReturnBuffer.write('id*', 0, 3, 'ascii');
  hashed.copy(opReturnBuffer, 3);
  opReturnBuffer.write(consensusHash, 23, 16, 'hex');

  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;

  var tx = makeTXbuilder();

  tx.addOutput(nullOutput, 0);
  tx.addOutput(preorderAddress, _utils.DUST_MINIMUM);
  tx.addOutput(burnAddress, btcBurnAmount);

  return tx.buildIncomplete();
}

function makeNamespaceRevealSkeleton(namespace, revealAddress) {
  /*
   Format:
    0     2   3    7     8     9    10   11   12   13   14    15    16    17       18      20     39
   |-----|---|----|-----|-----|----|----|----|----|----|-----|-----|-----|--------|-------|-------|
   magic  op  life coeff. base 1-2  3-4  5-6  7-8  9-10 11-12 13-14 15-16 nonalpha version  ns ID
                                                  bucket exponents        no-vowel
                                                                          discounts
  */
  var hexPayload = namespace.toHexPayload();

  var opReturnBuffer = Buffer.alloc(3 + hexPayload.length / 2);
  opReturnBuffer.write('id&', 0, 3, 'ascii');
  opReturnBuffer.write(hexPayload, 3, hexPayload.length / 2, 'hex');

  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;
  var tx = makeTXbuilder();

  tx.addOutput(nullOutput, 0);
  tx.addOutput(revealAddress, _utils.DUST_MINIMUM);

  return tx.buildIncomplete();
}

function makeNamespaceReadySkeleton(namespaceID) {
  /*
   Format:
    0     2  3  4           23
   |-----|--|--|------------|
   magic op  .  ns_id
    */
  var opReturnBuffer = Buffer.alloc(3 + namespaceID.length + 1);
  opReturnBuffer.write('id!', 0, 3, 'ascii');
  opReturnBuffer.write('.' + namespaceID, 3, namespaceID.length + 1, 'ascii');

  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;
  var tx = makeTXbuilder();

  tx.addOutput(nullOutput, 0);

  return tx.buildIncomplete();
}

function makeNameImportSkeleton(name, recipientAddr, zonefileHash) {
  /*
   Format:
     0    2  3                             39
    |----|--|-----------------------------|
    magic op   name.ns_id (37 bytes)
    Output 0: the OP_RETURN
   Output 1: the recipient
   Output 2: the zonefile hash
  */
  if (zonefileHash.length !== 40) {
    throw new Error('Invalid zonefile hash: must be 20 bytes hex-encoded');
  }

  var network = _config.config.network;
  var opReturnBuffer = Buffer.alloc(3 + name.length);
  opReturnBuffer.write('id;', 0, 3, 'ascii');
  opReturnBuffer.write(name, 3, name.length, 'ascii');

  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;

  var tx = makeTXbuilder();
  var zonefileHashB58 = _bitcoinjsLib2.default.address.toBase58Check(new Buffer(zonefileHash, 'hex'), network.layer1.pubKeyHash);

  tx.addOutput(nullOutput, 0);
  tx.addOutput(recipientAddr, _utils.DUST_MINIMUM);
  tx.addOutput(zonefileHashB58, _utils.DUST_MINIMUM);

  return tx.buildIncomplete();
}

function makeAnnounceSkeleton(messageHash) {
  /*
    Format:
     0    2  3                             23
    |----|--|-----------------------------|
    magic op   message hash (160-bit)
  */
  if (messageHash.length !== 40) {
    throw new Error('Invalid message hash: must be 20 bytes hex-encoded');
  }

  var opReturnBuffer = Buffer.alloc(3 + messageHash.length / 2);
  opReturnBuffer.write('id#', 0, 3, 'ascii');
  opReturnBuffer.write(messageHash, 3, messageHash.length, 'hex');

  var nullOutput = _bitcoinjsLib2.default.payments.embed({ data: [opReturnBuffer] }).output;

  var tx = makeTXbuilder();

  tx.addOutput(nullOutput, 0);
  return tx.buildIncomplete();
}