const assert = require('nanoassert')
const ec = require('./fe25519_25')

const crypto_scalarmult_ed25519_BYTES = 32
const crypto_scalarmult_ed25519_SCALARBYTES = 32

module.exports = {
  crypto_scalarmult_ed25519,
  crypto_scalarmult_ed25519_noclamp,
  crypto_scalarmult_ed25519_base,
  crypto_scalarmult: crypto_scalarmult_curve25519,
  crypto_scalarmult_base: crypto_scalarmult_curve25519_base,
  crypto_scalarmult_ed25519_base_noclamp,
  crypto_scalarmult_curve25519,
  crypto_scalarmult_curve25519_1,
  crypto_scalarmult_curve25519_base,
  crypto_scalarmult_ristretto255,
  crypto_scalarmult_ristretto255_base,
  crypto_scalarmult_BYTES: crypto_scalarmult_ed25519_BYTES,
  crypto_scalarmult_SCALARBYTES: crypto_scalarmult_ed25519_SCALARBYTES,
  crypto_scalarmult_ed25519_BYTES,
  crypto_scalarmult_ed25519_SCALARBYTES,
  crypto_scalarmult_curve25519_BYTES: 32,
  crypto_scalarmult_curve25519_SCALARBYTES: 32
}

const _121666buf = Buffer.alloc(32)
_121666buf.writeUInt32LE(121666)

const _121666 = ec.fe25519()
ec.fe25519_frombytes(_121666, _121666buf)

function _crypto_scalarmult_ed25519_is_inf (s) {
  var c
  var i

  c = s[0] ^ 0x01
  for (i = 1; i < 31; i++) {
    c |= s[i]
  }
  c |= s[31] & 0x7f

  return ((c - 1) >> 8) & 1
}

function _crypto_scalarmult_ed25519_clamp (k) {
  k[0] &= 248
  k[31] |= 64
}

function _crypto_scalarmult_ed25519 (q, n, p, clamp) {
  var Q = ec.ge3()
  var P = ec.ge3()
  var t = q.slice()
  var i

  if (ec.ge25519_is_canonical(p) == 0 || ec.ge25519_has_small_order(p) != 0 ||
      ec.ge25519_frombytes(P, p) != 0 || ec.ge25519_is_on_main_subgroup(P) == 0) {
    throw new Error('Invalid base point')
  }

  for (i = 0; i < 32; ++i) {
    t[i] = n[i]
  }

  if (clamp !== 0) {
    _crypto_scalarmult_ed25519_clamp(t)
  }

  t[31] &= 127

  ec.ge25519_scalarmult(Q, t, P)
  ec.ge25519_p3_tobytes(q, Q)

  if (_crypto_scalarmult_ed25519_is_inf(q) != 0 || sodium_is_zero(n, 32)) {
    throw new Error('Point multiplication failed')
  }

  return 0
}

function crypto_scalarmult_ed25519 (q, n, p) {
  assert(q instanceof Uint8Array && q.byteLength === 32)
  assert(n instanceof Uint8Array && n.byteLength === 32)
  assert(p instanceof Uint8Array && p.byteLength === 32)

  return _crypto_scalarmult_ed25519(q, n, p, 1)
}

function crypto_scalarmult_ed25519_noclamp (q, n, p) {
  assert(q instanceof Uint8Array && q.byteLength === 32)
  assert(n instanceof Uint8Array && n.byteLength === 32)
  assert(p instanceof Uint8Array && p.byteLength === 32)

  return _crypto_scalarmult_ed25519(q, n, p, 0)
}

function _crypto_scalarmult_ed25519_base (q, n, clamp) {
  var t = q.slice()
  var Q = ec.ge3()
  var i

  for (i = 0; i < 32; ++i) {
    t[i] = n[i]
  }

  if (clamp !== 0) {
    _crypto_scalarmult_ed25519_clamp(t)
  }

  t[31] &= 127

  ec.ge25519_scalarmult_base(Q, t)
  ec.ge25519_p3_tobytes(q, Q)
  if (_crypto_scalarmult_ed25519_is_inf(q) !== 0 || sodium_is_zero(n, 32)) {
    return -1
  }
  return 0
}

function crypto_scalarmult_ed25519_base(q, n) {
  assert(q instanceof Uint8Array && q.byteLength === 32)
  assert(n instanceof Uint8Array && n.byteLength === 32)

  return _crypto_scalarmult_ed25519_base(q, n, 1)
}

function crypto_scalarmult_ed25519_base_noclamp (q, n) {
  return _crypto_scalarmult_ed25519_base(q, n, 0)
}

function crypto_scalarmult_ed25519_bytes () {
  return crypto_scalarmult_ed25519_BYTES
}

function crypto_scalarmult_ed25519_scalarbytes () {
  return crypto_scalarmult_ed25519_SCALARBYTES
}

function crypto_scalarmult_ristretto255 (q, n, p) {
  assert(q instanceof Uint8Array && q.byteLength === 32)
  assert(n instanceof Uint8Array && n.byteLength === 32)
  assert(p instanceof Uint8Array && p.byteLength === 32)

  var t = q.slice()
  var Q = ec.ge3()
  var P = ec.ge3()
  var i

  if (ec.ristretto255_frombytes(P, p) != 0) {
    return -1
  }
  for (i = 0; i < 32; ++i) {
    t[i] = n[i]
  }
  t[31] &= 127
  ec.ge25519_scalarmult(Q, t, P)
  ec.ristretto255_p3_tobytes(q, Q)
  if (sodium_is_zero(q, 32)) {
    return -1
  }
  return 0
}

function crypto_scalarmult_ristretto255_base (q, n) {
  var t = q.slice
  var Q = ec.ge3()
  var i

  for (i = 0; i < 32; ++i) {
    t[i] = n[i]
  }

  t[31] &= 127

  ec.ge25519_scalarmult_base(Q, t)
  ec.ristretto255_p3_tobytes(q, Q)
  if (sodium_is_zero(q, 32)) {
    return -1
  }
  return 0
}

function crypto_scalarmult_ristretto255_bytes () {
  return crypto_scalarmult_ristretto255_BYTES
}

function crypto_scalarmult_ristretto255_scalarbytes () {
  return crypto_scalarmult_ristretto255_SCALARBYTES
}

function has_small_order (s) {
  assert(s instanceof Uint8Array && s.byteLength === 32)

  const blacklist = [
    /* 0 (order 4) */
    [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ],
    /* 1 (order 1) */
    [ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ],
    /* 325606250916557431795983626356110631294008115727848805560023387167927233504
       (order 8) */
    [ 0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3,
      0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32,
      0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00 ],
    /* 39382357235489614581723060781553021112529911719440698176882885853963445705823
       (order 8) */
    [ 0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1,
      0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c,
      0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57 ],
    /* p-1 (order 2) */
    [ 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f ],
    /* p (=0, order 4) */
    [ 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f ],
    /* p+1 (=1, order 1) */
    [ 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f ]
  ]

  const c = Buffer.alloc(7)

  var k
  var i, j

  assert(blacklist.length === 7)
  for (j = 0; j < 31; j++) {
    for (i = 0; i < 7; i++) {
      c[i] |= s[j] ^ blacklist[i][j]
    }
  }
  for (i = 0; i < 7; i++) {
    c[i] |= (s[j] & 0x7f) ^ blacklist[i][j]
  }
  k = 0
  for (i = 0; i < 7; i++) {
    k |= (c[i] - 1)
  }
  return ((k >> 8) & 1)
}

function crypto_scalarmult_curve25519 (q, n, p) {
  var t = q.slice()
  var i
  var x1 = ec.fe25519()
  var x2 = ec.fe25519()
  var z2 = ec.fe25519()
  var x3 = ec.fe25519()
  var z3 = ec.fe25519()
  var tmp0 = ec.fe25519()
  var tmp1 = ec.fe25519()
  var pos
  var swap
  var b

  if (has_small_order(p)) {
    return -1;
  }
  for (i = 0; i < 32; i++) {
    t[i] = n[i]
  }
  t[0] &= 248
  t[31] &= 127
  t[31] |= 64
  ec.fe25519_frombytes(x1, p)
  ec.fe25519_1(x2)
  ec.fe25519_0(z2)
  ec.fe25519_copy(x3, x1)
  ec.fe25519_1(z3)

  for (pos = 254; pos >= 0; --pos) {
    b = t[Math.floor(pos / 8)] >> (pos & 7)
    b &= 1
    swap ^= b

    ec.fe25519_cswap(x2, x3, swap)
    ec.fe25519_cswap(z2, z3, swap)
    swap = b
    ec.fe25519_sub(tmp0, x3, z3)

    ec.fe25519_sub(tmp1, x2, z2)
    ec.fe25519_add(x2, x2, z2)
    ec.fe25519_add(z2, x3, z3)
    ec.fe25519_mul(z3, tmp0, x2)
    ec.fe25519_mul(z2, z2, tmp1)
    ec.fe25519_sq(tmp0, tmp1)
    ec.fe25519_sq(tmp1, x2)
    ec.fe25519_add(x3, z3, z2)
    ec.fe25519_sub(z2, z3, z2)
    ec.fe25519_mul(x2, tmp1, tmp0)
    ec.fe25519_sub(tmp1, tmp1, tmp0)
    ec.fe25519_sq(z2, z2)
    // TODO
    // ec.fe25519_mul32(z3, tmp1, 121666)
    ec.fe25519_mul(z3, tmp1, _121666)
    ec.fe25519_sq(x3, x3)
    ec.fe25519_add(tmp0, tmp0, z3)
    ec.fe25519_mul(z3, x1, z2)
    ec.fe25519_mul(z2, tmp1, tmp0)
  }

  ec.fe25519_cswap(x2, x3, swap)
  ec.fe25519_cswap(z2, z3, swap)

  ec.fe25519_invert(z2, z2)
  ec.fe25519_mul(x2, x2, z2)
  ec.fe25519_tobytes(q, x2)

  return 0
}

function crypto_scalarmult_curve25519_1 (q, n, p) {
  var t = q.slice()
  var i
  var x1 = ec.fe25519()
  var x2 = ec.fe25519()
  var z2 = ec.fe25519()
  var x3 = ec.fe25519()
  var z3 = ec.fe25519()
  var pos
  var swap
  var b
  var _q = Buffer.alloc(32)

  if (has_small_order(p)) {
    return -1;
  }
  t.set(n)
  t[0] &= 248
  t[31] &= 127
  t[31] |= 64

  ec.fe25519_frombytes(x1, p)
  swap = ec.scalarmult_curve25519_inner_loop(x1, x2, t)

  ec.fe25519_tobytes(q, x2)

  return 0
}

function edwards_to_montgomery(montgomeryX, edwardsY, edwardsZ) {
  var tempX = ec.fe25519()
  var tempZ = ec.fe25519()

  ec.fe25519_add(tempX, edwardsZ, edwardsY)
  ec.fe25519_sub(tempZ, edwardsZ, edwardsY)
  ec.fe25519_invert(tempZ, tempZ)
  ec.fe25519_mul(montgomeryX, tempX, tempZ)
}

function crypto_scalarmult_curve25519_base (q, n) {
  var t = q.slice()
  var Q = ec.ge3()
  const pk = ec.fe25519()

  var i

  for (i = 0; i < 32; i++) {
    t[i] = n[i]
  }

  t[0] &= 248
  t[31] |= 64
  t[31] &= 127

  ec.ge25519_scalarmult_base(Q, t)
  edwards_to_montgomery(pk, Q[1], Q[2]);
  ec.fe25519_tobytes(q, pk)

  return 0
}

function print32 (num) {
  if (num < 0) return print32(0x100000000 + num)
  console.log(num.toString(16).padStart(16, '0'))
}

function printfe (fe) {
  for (let i of fe) print32(i)
}

function sodium_is_zero (n) {
  let i
  let d = 0

  for (let i = 0; i < n.length; i++) {
    d |= n[i]
  }

  return 1 & ((d - 1) >> 8)
}
