let ecc = require('tiny-secp256k1')
let randomBytes = require('randombytes')
let typeforce = require('typeforce')
let types = require('./types')
let wif = require('wif')

let NETWORKS = require('./networks')

// TODO: why is the function name toJSON weird?
function isPoint (x) { return ecc.isPoint(x) }
let isOptions = typeforce.maybe(typeforce.compile({
  compressed: types.maybe(types.Boolean),
  network: types.maybe(types.Network)
}))

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

  this.compressed = options.compressed === undefined ? true : options.compressed
  this.network = options.network || NETWORKS.bitcoin

  this.__d = d || null
  this.__Q = null
  if (Q) this.__Q = ecc.pointCompress(Q, this.compressed)
}

ECPair.prototype.getNetwork = function () {
  return this.network
}

ECPair.prototype.getPrivateKey = function () {
  return this.__d
}

ECPair.prototype.getPublicKey = function () {
  if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed)
  return this.__Q
}

ECPair.prototype.toWIF = function () {
  if (!this.__d) throw new Error('Missing private key')
  return wif.encode(this.network.wif, this.__d, this.compressed)
}

ECPair.prototype.sign = function (hash) {
  if (!this.__d) throw new Error('Missing private key')
  return ecc.sign(hash, this.__d)
}

ECPair.prototype.verify = function (hash, signature) {
  return ecc.verify(hash, this.getPublicKey(), signature)
}

function fromPrivateKey (buffer, options) {
  typeforce(types.Buffer256bit, buffer)
  if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)')
  typeforce(isOptions, options)

  return new ECPair(buffer, null, options)
}

function fromPublicKey (buffer, options) {
  typeforce(isPoint, buffer)
  typeforce(isOptions, options)
  return new ECPair(null, buffer, options)
}

function fromWIF (string, network) {
  let decoded = wif.decode(string)
  let version = decoded.version

  // list of networks?
  if (types.Array(network)) {
    network = network.filter(function (x) {
      return version === x.wif
    }).pop()

    if (!network) throw new Error('Unknown network version')

  // otherwise, assume a network object (or default to bitcoin)
  } else {
    network = network || NETWORKS.bitcoin

    if (version !== network.wif) throw new Error('Invalid network version')
  }

  return fromPrivateKey(decoded.privateKey, {
    compressed: decoded.compressed,
    network: network
  })
}

function makeRandom (options) {
  typeforce(isOptions, options)
  options = options || {}
  let rng = options.rng || randomBytes

  let d
  do {
    d = rng(32)
    typeforce(types.Buffer256bit, d)
  } while (!ecc.isPrivate(d))

  return fromPrivateKey(d, options)
}

module.exports = {
  makeRandom,
  fromPrivateKey,
  fromPublicKey,
  fromWIF
}