'use strict'
var EthJSTX = require('ethereumjs-tx')
var EthJSBlock = require('ethereumjs-block')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN

function TxRunner (executionContext, vmaccounts, opts) {
  this.executionContext = executionContext
  this.web3 = executionContext.web3()
  this.vm = executionContext.vm()
  this.queueTxs = opts.queueTxs
  this.personalMode = opts.personalMode
  this.blockNumber = 0
  if (this.executionContext.isVM()) {
    this.blockNumber = 1150000 // The VM is running in Homestead mode, which started at this block.
  }
  this.running = false
  this.pendingTxs = []
  this.vmaccounts = vmaccounts
}

TxRunner.prototype.rawRun = function (args, cb) {
  this.pendingTxs.push({tx: args, cb: cb})
  this.execute()
}

TxRunner.prototype.execute = function () {
  var self = this
  if (this.running || this.pendingTxs.length === 0) {
    return
  }
  var args = this.pendingTxs[0].tx
  var cb = this.pendingTxs[0].cb
  this.pendingTxs.shift()
  var callback = function (error, result) {
    cb(error, result)
    self.running = false
    self.execute()
  }

  this.running = true
  var from = args.from
  var to = args.to
  var data = args.data
  if (data.slice(0, 2) !== '0x') {
    data = '0x' + data
  }
  var value = args.value
  var gasLimit = args.gasLimit

  var tx
  if (!self.executionContext.isVM()) {
    tx = {
      from: from,
      to: to,
      data: data,
      value: value
    }
    if (args.useCall) {
      tx.gas = gasLimit
      self.web3.eth.call(tx, function (error, result) {
        callback(error, {
          result: result,
          transactionHash: result.transactionHash
        })
      })
    } else {
      self.web3.eth.estimateGas(tx, function (err, gasEstimation) {
        if (err) {
          return callback(err, gasEstimation)
        }
        var blockGasLimit = self.executionContext.currentblockGasLimit()
        // NOTE: estimateGas very likely will return a large limit if execution of the code failed
        //       we want to be able to run the code in order to debug and find the cause for the failure
        if (gasEstimation > gasLimit) {
          return callback('Gas required exceeds limit: ' + gasLimit)
        }
        if (gasEstimation > blockGasLimit) {
          return callback('Gas required exceeds block gas limit: ' + gasLimit)
        }
        tx.gas = gasEstimation
        var sendTransaction = self.personalMode ? self.web3.personal.sendTransaction : self.web3.eth.sendTransaction
        sendTransaction(tx, function (err, resp) {
          if (err) {
            return callback(err, resp)
          }

          tryTillResponse(self.web3, resp, callback)
        })
      })
    }
  } else {
    try {
      var account = self.vmaccounts[from]
      if (!account) {
        return cb('Invalid account selected')
      }
      tx = new EthJSTX({
        nonce: new BN(account.nonce++),
        gasPrice: new BN(1),
        gasLimit: new BN(gasLimit, 10),
        to: to,
        value: new BN(value, 10),
        data: new Buffer(data.slice(2), 'hex')
      })
      tx.sign(account.privateKey)

      const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ]
      const difficulties = [ new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10) ]
      var block = new EthJSBlock({
        header: {
          timestamp: new Date().getTime() / 1000 | 0,
          number: self.blockNumber,
          coinbase: coinbases[self.blockNumber % coinbases.length],
          difficulty: difficulties[self.blockNumber % difficulties.length],
          gasLimit: new BN(gasLimit, 10).imuln(2)
        },
        transactions: [],
        uncleHeaders: []
      })
      if (!args.useCall) {
        ++self.blockNumber
      } else {
        self.vm.stateManager.checkpoint()
      }

      self.vm.runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) {
        if (args.useCall) {
          self.vm.stateManager.revert(function () {})
        }
        callback(err, {
          result: result,
          transactionHash: ethJSUtil.bufferToHex(new Buffer(tx.hash()))
        })
      })
    } catch (e) {
      callback(e, null)
    }
  }
}

function tryTillResponse (web3, txhash, done) {
  web3.eth.getTransactionReceipt(txhash, function (err, result) {
    if (!err && !result) {
      // Try again with a bit of delay
      setTimeout(function () { tryTillResponse(web3, txhash, done) }, 500)
    } else {
      done(err, {
        result: result,
        transactionHash: result.transactionHash
      })
    }
  })
}

module.exports = TxRunner

@@ -569,31 +569,8 @@
   %7D%0A
-  this.running = false%0A
   th
@@ -585,18 +585,18 @@
 ngTxs = 
-%5B%5D
+%7B%7D
 %0A  this.
@@ -678,429 +678,118 @@
 %7B%0A  
-this.pendingTxs.push(%7Btx: args, cb: cb%7D)%0A  this.execute()%0A%7D%0A%0ATxRunner.prototype.execute = function () %7B%0A  var self = this%0A  if (this.running %7C%7C this.pendingTxs.length === 0) %7B%0A    return%0A  %7D%0A  var args = this.pendingTxs%5B0%5D.tx%0A  var cb = this.pendingTxs%5B0%5D.cb%0A  this.pendingTxs.shift()%0A  var callback = function (error, result) %7B%0A    cb(error, result)%0A    self.running = false%0A    self.execute()%0A  %7D%0A%0A  this.running = true
+run(this, args, Date.now(), cb)%0A%7D%0A%0ATxRunner.prototype.execute = function (args, callback) %7B%0A  var self = this%0A
 %0A  v
@@ -4368,16 +4368,199 @@
   %7D)%0A%7D%0A%0A
+function run (self, tx, stamp, callback) %7B%0A  self.pendingTxs%5Bstamp%5D = tx%0A  self.execute(tx, (error, result) =%3E %7B%0A    delete self.pendingTxs%5Bstamp%5D%0A    callback(error, result)%0A  %7D)%0A%7D%0A%0A
 module.e

make tx run in parallel