text: 'use strict';
  
  var fs = require('fs');
  var util = require('util');
  var bindings = require('bindings')('bitcoind.node');
  var mkdirp = require('mkdirp');
  var bitcore = require('bitcore-lib');
  var $ = bitcore.util.preconditions;
  var index = require('../');
  var log = index.log;
  var Service = require('../service');
  
  /**
   * Provides an interface to native bindings to [Bitcoin Core](https://github.com/bitcoin/bitcoin)
   * compiled as a static library. The C++ bindings can be found at `src/libbitcoind.cc`
   * @param {Object} options
   * @param {Node} options.node - A reference to the node
   */
  function Bitcoin(options) {
    if (!(this instanceof Bitcoin)) {
      return new Bitcoin(options);
    }
  
    this._reindex = false;
    this._reindexWait = 1000;
    Service.call(this, options);
    $.checkState(this.node.datadir, 'Node is missing datadir property');
  }
  
  util.inherits(Bitcoin, Service);
  
  Bitcoin.dependencies = [];
  
  Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n';
  
  Bitcoin.prototype._loadConfiguration = function() {
    /* jshint maxstatements: 25 */
  
    $.checkArgument(this.node.datadir, 'Please specify "datadir" in configuration options');
    var configPath = this.node.datadir + '/bitcoin.conf';
    this.configuration = {};
  
    if (!fs.existsSync(this.node.datadir)) {
      mkdirp.sync(this.node.datadir);
    }
  
    if (!fs.existsSync(configPath)) {
      var defaultConfig = Bitcoin.DEFAULT_CONFIG;
      if(this.node.https && this.node.httpsOptions) {
        defaultConfig += 'rpcssl=1\n';
        defaultConfig += 'rpcsslprivatekeyfile=' + this.node.httpsOptions.key + '\n';
        defaultConfig += 'rpcsslcertificatechainfile=' + this.node.httpsOptions.cert + '\n';
      }
      fs.writeFileSync(configPath, defaultConfig);
    }
  
    var file = fs.readFileSync(configPath);
    var unparsed = file.toString().split('\n');
    for(var i = 0; i < unparsed.length; i++) {
      var line = unparsed[i];
      if (!line.match(/^\#/) && line.match(/\=/)) {
        var option = line.split('=');
        var value;
        if (!Number.isNaN(Number(option[1]))) {
          value = Number(option[1]);
        } else {
          value = option[1];
        }
        this.configuration[option[0]] = value;
      }
    }
  
    $.checkState(
      this.configuration.txindex && this.configuration.txindex === 1,
      'Txindex option is required in order to use most of the features of bitcore-node. ' +
        'Please add "txindex=1" to your configuration and reindex an existing database if ' +
        'necessary with reindex=1'
    );
  
    if (this.configuration.reindex && this.configuration.reindex === 1) {
      log.warn('Reindex option is currently enabled. This means that bitcoind is undergoing a reindex. ' +
        'The reindex flag will start the index from beginning every time the node is started, so it ' +
        'should be removed after the reindex has been initiated. Once the reindex is complete, the rest ' +
        'of bitcore-node services will start.');
      this._reindex = true;
    }
  
  };
  
  Bitcoin.prototype._onTipUpdate = function(result) {
    if (result) {
      // Emit and event that the tip was updated
      this.height = result;
      this.emit('tip', result);
  
      // TODO stopping status
      if(!this.node.stopping) {
        var percentage = this.syncPercentage();
        log.info('Bitcoin Height:', this.height, 'Percentage:', percentage);
      }
  
      // Recursively wait until the next update
      bindings.onTipUpdate(this._onTipUpdate.bind(this));
    }
  };
  
  Bitcoin.prototype._registerEventHandlers = function() {
    var self = this;
  
    // Set the height and emit a new tip
    bindings.onTipUpdate(self._onTipUpdate.bind(this));
  
    // Register callback function to handle transactions entering the mempool
    bindings.startTxMon(function(txs) {
      for(var i = 0; i < txs.length; i++) {
        self.emit('tx', txs[i]);
      }
    });
  
    // Register callback function to handle transactions leaving the mempool
    bindings.startTxMonLeave(function(txs) {
      for(var i = 0; i < txs.length; i++) {
        self.emit('txleave', txs[i]);
      }
    });
  };
  
  Bitcoin.prototype._onReady = function(result, callback) {
    var self = this;
  
    self._registerEventHandlers();
  
    var info = self.getInfo();
    self.height = info.blocks;
  
    self.getBlock(0, function(err, block) {
      if (err) {
        return callback(err);
      }
      self.genesisBuffer = block;
      self.emit('ready', result);
      log.info('Bitcoin Daemon Ready');
      callback();
    });
  
  };
  
  /**
   * Called by Node to start the service
   * @param {Function} callback
   */
  Bitcoin.prototype.start = function(callback) {
    var self = this;
  
    this._loadConfiguration();
  
    bindings.start({
      datadir: this.node.datadir,
      network: this.node.network.name
    }, function(err) {
      if(err) {
        return callback(err);
      }
      // Wait until the block chain is ready
      bindings.onBlocksReady(function(err, result) {
        if (err) {
          return callback(err);
        }
        if (self._reindex) {
          var interval = setInterval(function() {
            var percentSynced = bindings.syncPercentage();
            log.info("Bitcoin Core Daemon Reindex Percentage: " + percentSynced);
            if (percentSynced >= 100) {
              self._reindex = false;
              self._onReady(result, callback);
              clearInterval(interval);
            }
          }, self._reindexWait);
  
        }
        else {
          self._onReady(result, callback);
        }
      });
    });
  };
  
  /**
   * Helper to determine the state of the database.
   * @returns {Boolean} If the database is fully synced
   */
  Bitcoin.prototype.isSynced = function() {
    return bindings.isSynced();
  };
  
  /**
   * Helper to determine the progress of the database.
   * @returns {Number} An estimated percentage of the syncronization status
   */
  Bitcoin.prototype.syncPercentage = function() {
    return bindings.syncPercentage();
  };
  
  /**
   * Will retrieve a block as a Node.js Buffer from disk.
   * @param {String|Number} block - A block hash or block height number
   */
  Bitcoin.prototype.getBlock = function(block, callback) {
    return bindings.getBlock(block, callback);
  };
  
  /**
   * Will return the spent status of an output (not including the mempool)
   * @param {String} txid - The transaction hash
   * @param {Number} outputIndex - The output index in the transaction
   * @returns {Boolean} If the output has been spent
   */
  Bitcoin.prototype.isSpent = function(txid, outputIndex) {
    return bindings.isSpent(txid, outputIndex);
  };
  
  /**
   * Will return the block index information, the output will have the format:
   * {
   *   prevHash: '7194fcf33f58c96720f88f21ab28c34ebc5638c5f88d7838517deb27313b59de',
   *   hash: '7c5caf0af1bf16e3467b275a3b408bc1d251bff3c25be20cb727c47b66a7b216',
   *   chainWork: '0000000000000000000000000000000000000000000000000000000000000016',
   *   height: 10
   * }
   * @param {String|Number} block - A block hash or block height
   * @returns {Object}
   */
  Bitcoin.prototype.getBlockIndex = function(block) {
    return bindings.getBlockIndex(block);
  };
  
  /**
   * Will return if the block is a part of the main chain.
   * @param {String} blockHash
   * @returns {Boolean}
   */
  Bitcoin.prototype.isMainChain = function(blockHash) {
    return bindings.isMainChain(blockHash);
  };
  
  /**
   * Will estimate the fee per kilobyte.
   * @param {Number} blocks - The number of blocks for the transaction to be confirmed.
   * @returns {Number}
   */
  Bitcoin.prototype.estimateFee = function(blocks) {
    return bindings.estimateFee(blocks);
  };
  
  /**
   * Will add a transaction to the mempool and relay to connected peers, the function
   * will throw an error if there were validation problems.
   * @param {String} transaction - The hex string of the transaction
   * @param {Boolean} allowAbsurdFees - Enable large fees
   */
  Bitcoin.prototype.sendTransaction = function(transaction, allowAbsurdFees) {
    return bindings.sendTransaction(transaction, allowAbsurdFees);
  };
  
  /**
   * Will get a transaction as a Node.js Buffer from disk and the mempool.
   * @param {String} txid - The transaction hash
   * @param {Boolean} queryMempool - Include the mempool
   * @param {Function} callback
   */
  Bitcoin.prototype.getTransaction = function(txid, queryMempool, callback) {
    return bindings.getTransaction(txid, queryMempool, callback);
  };
  
  /**
   * Will get a transaction with additional information about the block, in the format:
   * {
   *   blockHash: '2725743288feae6bdaa976590af7cb12d7b535b5a242787de6d2789c73682ed1',
   *   height: 48,
   *   timestamp: 1442951110, // in seconds
   *   buffer: <Buffer...> // transaction buffer
   * }
   * @param {String} txid - The transaction hash
   * @param {Boolean} queryMempool - Include the mempool
   * @param {Function} callback
   */
  Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) {
    return bindings.getTransactionWithBlockInfo(txid, queryMempool, callback);
  };
  
  /**
   * Will return the entire mempool as an Array of transaction Buffers.
   * @returns {Array}
   */
  Bitcoin.prototype.getMempoolTransactions = function() {
    return bindings.getMempoolTransactions();
  };
  
  /**
   * Will add a transaction to the mempool without any validation. This is used
   * exclusively for testing purposes.
   * @param {String} transaction - The hex string for the transaction
   */
  Bitcoin.prototype.addMempoolUncheckedTransaction = function(transaction) {
    return bindings.addMempoolUncheckedTransaction(transaction);
  };
  
  /**
   * Will get the best block hash for the chain.
   * @returns {String}
   */
  Bitcoin.prototype.getBestBlockHash = function() {
    return bindings.getBestBlockHash();
  };
  
  /**
   * Will get the next block hash for a block hash.
   * @param {String} hash - The starting block hash
   * @returns {String}
   */
  Bitcoin.prototype.getNextBlockHash = function(hash) {
    return bindings.getNextBlockHash(hash);
  };
  
  /**
   * This will return information about the database in the format:
   * {
   *   version: 110000,
   *   protocolversion: 70002,
   *   blocks: 151,
   *   timeoffset: 0,
   *   connections: 0,
   *   difficulty: 4.6565423739069247e-10,
   *   testnet: false,
   *   relayfee: 1000,
   *   errors: ''
   * }
   */
  Bitcoin.prototype.getInfo = function() {
    return bindings.getInfo();
  };
  
  /**
   * Called by Node to stop the service.
   * @param {Function} callback
   */
  Bitcoin.prototype.stop = function(callback) {
    return bindings.stop(function(err, status) {
      if (err) {
        return callback(err);
      } else {
        log.info(status);
        return callback();
      }
    });
  };
  
  module.exports = Bitcoin;
  