text: 'use strict';
  
  var bitcore = require('bitcore-lib');
  var _ = bitcore.deps._;
  var $ = bitcore.util.preconditions;
  var common = require('./common');
  var async = require('async');
  
  var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;
  
  function TxController(node) {
    this.node = node;
  }
  
  TxController.prototype.show = function(req, res) {
    if (req.transaction) {
      res.jsonp(req.transaction);
    }
  };
  
  /**
   * Find transaction by hash ...
   */
  TxController.prototype.transaction = function(req, res, next, txid) {
    var self = this;
  
    this.node.getTransactionWithBlockInfo(txid, true, function(err, transaction) {
      if (err && err instanceof self.node.errors.Transaction.NotFound) {
        return common.handleErrors(null, res);
      } else if(err) {
        return common.handleErrors(err, res);
      }
  
      transaction.populateInputs(self.node.services.db, [], function(err) {
        if(err) {
          return res.send({
            error: err.toString()
          });
        }
  
        self.transformTransaction(transaction, function(err, transformedTransaction) {
          if (err) {
            return common.handleErrors(err, res);
          }
          req.transaction = transformedTransaction;
          next();
        });
  
      });
    });
  };
  
  TxController.prototype.transformTransaction = function(transaction, callback) {
    $.checkArgument(_.isFunction(callback));
    var self = this;
    var txid = transaction.id;
    var txObj = transaction.toObject();
  
    var confirmations = 0;
    if(transaction.__height >= 0) {
      confirmations = this.node.services.db.tip.__height - transaction.__height + 1;
    }
  
    var transformed = {
      txid: txObj.hash,
      version: txObj.version,
      locktime: txObj.nLockTime
    };
  
    if(transaction.isCoinbase()) {
      transformed.vin = [
        {
          coinbase: txObj.inputs[0].script,
          sequence: txObj.inputs[0].sequenceNumber,
          n: 0
        }
      ];
    } else {
      transformed.vin = txObj.inputs.map(this.transformInput.bind(this));
    }
  
    async.map(
      Object.keys(txObj.outputs),
      function(outputIndex, next) {
        outputIndex = parseInt(outputIndex);
        var output = txObj.outputs[outputIndex];
        self.transformOutput(txid, output, outputIndex, next);
      },
      function(err, vout) {
        if (err) {
          return callback(err);
        }
  
        transformed.vout = vout;
  
        transformed.blockhash = transaction.__blockHash;
        transformed.blockheight = transaction.__height;
        transformed.confirmations = confirmations;
        var time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000);
        transformed.time = time;
        if (transformed.confirmations) {
          transformed.blocktime = transformed.time;
        }
  
        if(transaction.isCoinbase()) {
          transformed.isCoinBase = true;
        }
  
        transformed.valueOut = transaction.outputAmount / 1e8;
        transformed.size = transaction.toBuffer().length;
        if(transaction.hasAllUtxoInfo()) {
          transformed.valueIn = transaction.inputAmount / 1e8;
          transformed.fees = transaction.getFee() / 1e8;
        }
  
        callback(null, transformed);
  
      }
    );
  
  };
  
  TxController.prototype.transformInput = function(input, index) {
    // Input scripts are validated and can be assumed to be valid
    var script = new bitcore.Script(input.script);
    var transformed = {
      txid: input.prevTxId,
      vout: input.outputIndex,
      scriptSig: {
        asm: script.toASM(),
        hex: input.script
      },
      sequence: input.sequenceNumber,
      n: index
    };
  
    if(input.output) {
      transformed.addr = bitcore.Script(input.output.script).toAddress(this.node.network).toString();
      transformed.valueSat = input.output.satoshis;
      transformed.value = input.output.satoshis / 1e8;
      transformed.doubleSpentTxID = null; // TODO
      //transformed.isConfirmed = null; // TODO
      //transformed.confirmations = null; // TODO
      //transformed.unconfirmedInput = null; // TODO
    }
  
    return transformed;
  };
  
  TxController.prototype.transformOutput = function(txid, output, index, callback) {
    var self = this;
    var transformed = {
      value: (output.satoshis / 1e8).toFixed(8),
      n: index,
      scriptPubKey: {
        hex: output.script,
        //reqSigs: null, // TODO
      }
      //spentTs: undefined // TODO
    };
  
    var script;
    try {
      // Output scripts can be invalid, so we need to try/catch
      script = new bitcore.Script(output.script);
    } catch (err) {
      script = false;
    }
    if (script) {
      transformed.scriptPubKey.asm = script.toASM();
      var address = script.toAddress(this.node.network);
      if (address) {
        transformed.scriptPubKey.addresses = [address.toString()];
        transformed.scriptPubKey.type = address.type;
      }
    }
  
    var options = {
      queryMempool: true
    };
  
    self.node.services.address.getInputForOutput(
      txid,
      index,
      options,
      function(err, inputResult) {
        if (err) {
          return callback(err);
        }
        if (inputResult) {
          transformed.spentTxId = inputResult.inputTxId;
          transformed.spentIndex = inputResult.inputIndex;
        }
        callback(null, transformed);
      }
    );
  };
  
  TxController.prototype.transformInvTransaction = function(transaction) {
    var self = this;
  
    var valueOut = 0;
    var vout = [];
    for (var i = 0; i < transaction.outputs.length; i++) {
      var output = transaction.outputs[i];
      valueOut += output.satoshis;
      if (output.script) {
        var address = output.script.toAddress(self.node.network);
        if (address) {
          var obj = {};
          obj[address.toString()] = output.satoshis;
          vout.push(obj);
        }
      }
    }
  
    var isRBF = _.any(_.pluck(transaction.inputs, 'sequenceNumber'), function(seq) {
      return seq < MAXINT - 1;
    });
  
    var transformed = {
      txid: transaction.hash,
      valueOut: valueOut / 1e8,
      vout: vout,
      isRBF: isRBF,
    };
  
    return transformed;
  };
  
  TxController.prototype.rawTransaction = function(req, res, next, txid) {
    var self = this;
  
    this.node.getTransaction(txid, true, function(err, transaction) {
      if (err && err instanceof self.node.errors.Transaction.NotFound) {
        return common.handleErrors(null, res);
      } else if(err) {
        return common.handleErrors(err, res);
      }
  
      req.rawTransaction = {
        'rawtx': transaction.toBuffer().toString('hex')
      };
  
      next();
    });
  };
  
  TxController.prototype.showRaw = function(req, res) {
    if (req.rawTransaction) {
      res.jsonp(req.rawTransaction);
    }
  };
  
  TxController.prototype.list = function(req, res) {
    var self = this;
  
    var blockHash = req.query.block;
    var address = req.query.address;
    var page = parseInt(req.query.pageNum) || 0;
    var pageLength = 10;
    var pagesTotal = 1;
  
    if(blockHash) {
      self.node.getBlock(blockHash, function(err, block) {
        if(err && err.message === 'Block not found.') {
          return common.handleErrors(null, res);
        } else if(err) {
          return common.handleErrors(err, res);
        }
  
        var blockInfo = self.node.services.bitcoind.getBlockIndex(block.hash);
        var txs = block.transactions;
        var totalTxs = txs.length;
  
        if(!_.isUndefined(page)) {
          txs = txs.splice(page * pageLength, pageLength);
          pagesTotal = Math.ceil(totalTxs / pageLength);
        }
  
        async.mapSeries(txs, function(tx, next) {
          tx.__blockHash = block.hash;
          tx.__height = blockInfo.height;
          tx.__timestamp = block.header.time;
  
          tx.populateInputs(self.node.services.db, [], function(err) {
            if(err) {
              return next(err);
            }
            self.transformTransaction(tx, next);
          });
        }, function(err, transformed) {
          if(err) {
            return common.handleErrors(err, res);
          }
  
          res.jsonp({
            pagesTotal: pagesTotal,
            txs: transformed
          });
        });
      });
    } else if(address) {
      var options = {
        from: page * pageLength,
        to: (page + 1) * pageLength
      };
  
      self.node.getAddressHistory(address, options, function(err, result) {
        if(err) {
          return common.handleErrors(err, res);
        }
  
        var txs = result.items.map(function(info) {
          return info.tx;
        }).filter(function(value, index, self) {
          return self.indexOf(value) === index;
        });
  
        async.map(
          txs,
          function(tx, next) {
            self.transformTransaction(tx, next);
          },
          function(err, transformed) {
            if (err) {
              return common.handleErrors(err, res);
            }
            res.jsonp({
              pagesTotal: Math.ceil(result.totalCount / pageLength),
              txs: transformed
            });
          }
        );
      });
    } else {
      return common.handleErrors(new Error('Block hash or address expected'), res);
    }
  };
  
  TxController.prototype.send = function(req, res) {
    this.node.sendTransaction(req.body.rawtx, function(err, txid) {
      if(err) {
        // TODO handle specific errors
        return common.handleErrors(err, res);
      }
  
      res.json({'txid': txid});
    });
  };
  
  module.exports = TxController;
  