var assert = require('assert')
var bs58check = require('bs58check')
var bcrypto = require('./crypto')
var ecdsa = require('./ecdsa')
var ecurve = require('ecurve')
var networks = require('./networks')
var randomBytes = require('randombytes')
var typeForce = require('typeforce')

var BigInteger = require('bigi')

function findNetworkByWIFVersion (version) {
  for (var networkName in networks) {
    var network = networks[networkName]

    if (network.wif === version) return network
  }

  throw new Error('Unknown network')
}

function ECPair (d, Q, options) {
  options = options || {}

  var compressed = options.compressed === undefined ? true : options.compressed
  var network = options.network === undefined ? networks.bitcoin : options.network

  typeForce('Boolean', compressed)
  assert('pubKeyHash' in network, 'Unknown pubKeyHash constants for network')

  if (d) {
    assert(d.signum() > 0, 'Private key must be greater than 0')
    assert(d.compareTo(ECPair.curve.n) < 0, 'Private key must be less than the curve order')

    assert(!Q, 'Unexpected publicKey parameter')
    this.d = d

  // enforce Q is a public key if no private key given
  } else {
    typeForce('Point', Q)
    this.__Q = Q
  }

  this.compressed = compressed
  this.network = network
}

Object.defineProperty(ECPair.prototype, 'Q', {
  get: function () {
    if (!this.__Q && this.d) {
      this.__Q = ECPair.curve.G.multiply(this.d)
    }

    return this.__Q
  }
})

// Public access to secp256k1 curve
ECPair.curve = ecurve.getCurveByName('secp256k1')

ECPair.fromPublicKeyBuffer = function (buffer, network) {
  var Q = ecurve.Point.decodeFrom(ECPair.curve, buffer)

  return new ECPair(null, Q, {
    compressed: Q.compressed,
    network: network
  })
}

ECPair.fromWIF = function (string) {
  var payload = bs58check.decode(string)
  var version = payload.readUInt8(0)
  var compressed

  if (payload.length === 34) {
    assert.strictEqual(payload[33], 0x01, 'Invalid compression flag')

    // truncate the version/compression bytes
    payload = payload.slice(1, -1)
    compressed = true

  // no compression flag
  } else {
    assert.equal(payload.length, 33, 'Invalid WIF payload length')

    // Truncate the version byte
    payload = payload.slice(1)
    compressed = false
  }

  var network = findNetworkByWIFVersion(version)
  var d = BigInteger.fromBuffer(payload)

  return new ECPair(d, null, {
    compressed: compressed,
    network: network
  })
}

ECPair.makeRandom = function (options) {
  options = options || {}

  var rng = options.rng || randomBytes
  var buffer = rng(32)
  typeForce('Buffer', buffer)
  assert.equal(buffer.length, 32, 'Expected 256-bit Buffer from RNG')

  var d = BigInteger.fromBuffer(buffer)
  d = d.mod(ECPair.curve.n)

  return new ECPair(d, null, options)
}

ECPair.prototype.toWIF = function () {
  assert(this.d, 'Missing private key')

  var bufferLen = this.compressed ? 34 : 33
  var buffer = new Buffer(bufferLen)

  buffer.writeUInt8(this.network.wif, 0)
  this.d.toBuffer(32).copy(buffer, 1)

  if (this.compressed) {
    buffer.writeUInt8(0x01, 33)
  }

  return bs58check.encode(buffer)
}

ECPair.prototype.getAddress = function () {
  var pubKey = this.getPublicKeyBuffer()
  var pubKeyHash = bcrypto.hash160(pubKey)

  var payload = new Buffer(21)
  payload.writeUInt8(this.network.pubKeyHash, 0)
  pubKeyHash.copy(payload, 1)

  return bs58check.encode(payload)
}

ECPair.prototype.getPublicKeyBuffer = function () {
  return this.Q.getEncoded(this.compressed)
}

ECPair.prototype.sign = function (hash) {
  assert(this.d, 'Missing private key')

  return ecdsa.sign(ECPair.curve, hash, this.d)
}

ECPair.prototype.verify = function (hash, signature) {
  return ecdsa.verify(ECPair.curve, hash, signature, this.Q)
}

module.exports = ECPair