text: 'use strict';
  
  var owsCommon = require('@owstack/ows-common');
  var keyLib = require('@owstack/key-lib');
  var BufferUtil = owsCommon.buffer;
  var inherits = require('inherits');
  var Input = require('./input');
  var Output = require('../output');
  var PublicKey = keyLib.PublicKey;
  var Script = require('../../script');
  var Sighash = require('../sighash');
  var Signature = keyLib.crypto.Signature;
  var Transaction = require('../transaction');
  var TransactionSignature = require('../signature');
  var lodash = owsCommon.deps.lodash;
  var $ = owsCommon.util.preconditions;
  
  /**
   * @constructor
   */
  function MultiSigInput(input, pubkeys, threshold, signatures) {
    Input.apply(this, arguments);
    var self = this;
    pubkeys = pubkeys || input.publicKeys;
    threshold = threshold || input.threshold;
    signatures = signatures || input.signatures;
    this.publicKeys = lodash.sortBy(pubkeys, function(publicKey) { return publicKey.toString('hex'); });
    $.checkState(Script.buildMultisigOut(this.publicKeys, threshold).equals(this.output.script),
      'Provided public keys don\'t match to the provided output script');
    this.publicKeyIndex = {};
    lodash.each(this.publicKeys, function(publicKey, index) {
      self.publicKeyIndex[publicKey.toString()] = index;
    });
    this.threshold = threshold;
    // Empty array of signatures
    this.signatures = signatures ? this._deserializeSignatures(signatures) : new Array(this.publicKeys.length);
  }
  inherits(MultiSigInput, Input);
  
  MultiSigInput.prototype.toObject = function() {
    var obj = Input.prototype.toObject.apply(this, arguments);
    obj.threshold = this.threshold;
    obj.publicKeys = lodash.map(this.publicKeys, function(publicKey) { return publicKey.toString(); });
    obj.signatures = this._serializeSignatures();
    return obj;
  };
  
  MultiSigInput.prototype._deserializeSignatures = function(signatures) {
    return lodash.map(signatures, function(signature) {
      if (!signature) {
        return undefined;
      }
      return new TransactionSignature(signature);
    });
  };
  
  MultiSigInput.prototype._serializeSignatures = function() {
    return lodash.map(this.signatures, function(signature) {
      if (!signature) {
        return undefined;
      }
      return signature.toObject();
    });
  };
  
  MultiSigInput.prototype.getSignatures = function(transaction, privateKey, index, sigtype) {
    $.checkState(this.output instanceof Output);
    sigtype = sigtype || Signature.SIGHASH_ALL;
  
    var self = this;
    var results = [];
    lodash.each(this.publicKeys, function(publicKey) {
      if (publicKey.toString() === privateKey.publicKey.toString()) {
        results.push(new TransactionSignature({
          publicKey: privateKey.publicKey,
          prevTxId: self.prevTxId,
          outputIndex: self.outputIndex,
          inputIndex: index,
          signature: Sighash.sign(transaction, privateKey, sigtype, index, self.output.script),
          sigtype: sigtype
        }));
      }
    });
  
    return results;
  };
  
  MultiSigInput.prototype.addSignature = function(transaction, signature) {
    $.checkState(!this.isFullySigned(), 'All needed signatures have already been added');
    $.checkArgument(!lodash.isUndefined(this.publicKeyIndex[signature.publicKey.toString()]),
      'Signature has no matching public key');
    $.checkState(this.isValidSignature(transaction, signature));
    this.signatures[this.publicKeyIndex[signature.publicKey.toString()]] = signature;
    this._updateScript();
    return this;
  };
  
  MultiSigInput.prototype._updateScript = function() {
    this.setScript(Script.buildMultisigIn(
      this.publicKeys,
      this.threshold,
      this._createSignatures()
    ));
    return this;
  };
  
  MultiSigInput.prototype._createSignatures = function() {
    return lodash.map(
      lodash.filter(this.signatures, function(signature) { return !lodash.isUndefined(signature); }),
      function(signature) {
        return BufferUtil.concat([
          signature.signature.toDER(),
          BufferUtil.integerAsSingleByteBuffer(signature.sigtype)
        ]);
      }
    );
  };
  
  MultiSigInput.prototype.clearSignatures = function() {
    this.signatures = new Array(this.publicKeys.length);
    this._updateScript();
  };
  
  MultiSigInput.prototype.isFullySigned = function() {
    return this.countSignatures() === this.threshold;
  };
  
  MultiSigInput.prototype.countMissingSignatures = function() {
    return this.threshold - this.countSignatures();
  };
  
  MultiSigInput.prototype.countSignatures = function() {
    return lodash.reduce(this.signatures, function(sum, signature) {
      return sum + (!!signature);
    }, 0);
  };
  
  MultiSigInput.prototype.publicKeysWithoutSignature = function() {
    var self = this;
    return lodash.filter(this.publicKeys, function(publicKey) {
      return !(self.signatures[self.publicKeyIndex[publicKey.toString()]]);
    });
  };
  
  MultiSigInput.prototype.isValidSignature = function(transaction, signature) {
    // FIXME: Refactor signature so this is not necessary
    signature.signature.nhashtype = signature.sigtype;
    return Sighash.verify(
      transaction,
      signature.signature,
      signature.publicKey,
      signature.inputIndex,
      this.output.script
    );
  };
  
  /**
   *
   * @param {Buffer[]} signatures
   * @param {PublicKey[]} publicKeys
   * @param {Transaction} transaction
   * @param {Integer} inputIndex
   * @param {Input} input
   * @returns {TransactionSignature[]}
   */
  MultiSigInput.normalizeSignatures = function(transaction, input, inputIndex, signatures, publicKeys) {
    return publicKeys.map(function (pubKey) {
      var signatureMatch = null;
      signatures = signatures.filter(function (signatureBuffer) {
        if (signatureMatch) {
          return true;
        }
  
        var signature = new TransactionSignature({
          signature: Signature.fromTxFormat(signatureBuffer),
          publicKey: pubKey,
          prevTxId: input.prevTxId,
          outputIndex: input.outputIndex,
          inputIndex: inputIndex,
          sigtype: Signature.SIGHASH_ALL
        });
  
        signature.signature.nhashtype = signature.sigtype;
        var isMatch = Sighash.verify(
            transaction,
            signature.signature,
            signature.publicKey,
            signature.inputIndex,
            input.output.script
        );
  
        if (isMatch) {
          signatureMatch = signature;
          return false;
        }
  
        return true;
      });
  
      return signatureMatch ? signatureMatch : null;
    });
  };
  
  MultiSigInput.OPCODES_SIZE = 1; // 0
  MultiSigInput.SIGNATURE_SIZE = 73; // size (1) + DER (<=72)
  
  MultiSigInput.prototype._estimateSize = function() {
    return MultiSigInput.OPCODES_SIZE +
      this.threshold * MultiSigInput.SIGNATURE_SIZE;
  };
  
  module.exports = MultiSigInput;
  