const b4a = require('b4a')
const assert = require('nanoassert')

const {
  ge25519_p3,
  ge25519_p1p1,
  ge25519_cached,
  ge25519_frombytes,
  ge25519_p3_tobytes,
  ge25519_p1p1_to_p3,
  ge25519_p3_to_cached,
  ge25519_is_on_curve,
  ge25519_is_canonical,
  ge25519_is_on_main_subgroup,
  ge25519_has_small_order,
  ge25519_add_cached,
  ge25519_sub_cached,
  ge25519_from_uniform,
  sc25519_reduce,
  sc25519_mul,
  sc25519_invert,
  sc25519_is_canonical
} = require('./fe25519_25.js')

const { randombytes_buf } = require('./randombytes')

const {
  sodium_add,
  sodium_sub,
  sodium_is_zero,
  sodium_memzero
} = require('./utils')

function crypto_core_ed25519_is_valid_point (p) {
  const p_p3 = ge25519_p3()

  if (ge25519_is_canonical(p) == 0 ||
      ge25519_has_small_order(p) != 0 ||
      ge25519_frombytes(p_p3, p) != 0 ||
      ge25519_is_on_curve(p_p3) == 0 ||
      ge25519_is_on_main_subgroup(p_p3) == 0) {
    return 0;
  }
  return 1;
}

function crypto_core_ed25519_add (r, p, q) {
  const p_p3 = ge25519_p3()
  const q_p3 = ge25519_p3()
  const r_p3 = ge25519_p3()

  const r_p1p1 = ge25519_p1p1()
  const q_cached = ge25519_cached()

  if (ge25519_frombytes(p_p3, p) != 0 || ge25519_is_on_curve(p_p3) == 0 ||
      ge25519_frombytes(q_p3, q) != 0 || ge25519_is_on_curve(q_p3) == 0) {
    throw new Error('Operands must be valid points.')
  }

  ge25519_p3_to_cached(q_cached, q_p3)
  ge25519_add_cached(r_p1p1, p_p3, q_cached)
  ge25519_p1p1_to_p3(r_p3, r_p1p1)
  ge25519_p3_tobytes(r, r_p3)
}

function crypto_core_ed25519_sub (r, p, q) {
  const p_p3 = ge25519_p3()
  const q_p3 = ge25519_p3()
  const r_p3 = ge25519_p3()

  const r_p1p1 = ge25519_p1p1()
  const q_cached = ge25519_cached()

  if (ge25519_frombytes(p_p3, p) != 0 || ge25519_is_on_curve(p_p3) == 0 ||
      ge25519_frombytes(q_p3, q) != 0 || ge25519_is_on_curve(q_p3) == 0) {
    throw new Error('Operands must be valid points.')
  }
  ge25519_p3_to_cached(q_cached, q_p3);
  ge25519_sub_cached(r_p1p1, p_p3, q_cached);
  ge25519_p1p1_to_p3(r_p3, r_p1p1);
  ge25519_p3_tobytes(r, r_p3);
}

function crypto_core_ed25519_from_uniform (p, r) {
    ge25519_from_uniform(p, r)
}

// const HASH_GE_L = 48

// function _string_to_points (px, n, ctx, msg, msg_len, hash_alg) {
//     const h = b4a.alloc(crypto_core_ed25519_HASHBYTES)
//     const h_be = b4a.alloc(2 * HASH_GE_L)

//     let i
//     let j

//     if (n > 2) {
//         // abort(); /* LCOV_EXCL_LINE */
//         throw new Error('abort')
//     }
//     if (core_h2c_string_to_hash(h_be, n * HASH_GE_L, ctx, msg, msg_len,
//                                 hash_alg) != 0) {
//         return -1;
//     }
//     COMPILER_ASSERT(sizeof h >= HASH_GE_L);
//     for (i = 0U; i < n; i++) {
//         for (j = 0U; j < HASH_GE_L; j++) {
//             h[j] = h_be[i * HASH_GE_L + HASH_GE_L - 1U - j];
//         }
//         memset(&h[j], 0, (sizeof h) - j);
//         ge25519_from_hash(&px[i * crypto_core_ed25519_BYTES], h);
//     }
//     return 0;
// }

// int
// crypto_core_ed25519_from_string(unsigned char p[crypto_core_ed25519_BYTES],
//                                 const char *ctx, msg,
//                                 size_t msg_len, int hash_alg)
// {
//     return _string_to_points(p, 1, ctx, msg, msg_len, hash_alg);
// }

// int
// crypto_core_ed25519_from_string_ro(unsigned char p[crypto_core_ed25519_BYTES],
//                                    const char *ctx, msg,
//                                    size_t msg_len, int hash_alg)
// {
//     unsigned char px[2 * crypto_core_ed25519_BYTES];

//     if (_string_to_points(px, 2, ctx, msg, msg_len, hash_alg) != 0) {
//         return -1;
//     }
//     return crypto_core_ed25519_add(p, &px[0], &px[crypto_core_ed25519_BYTES]);
// }


function crypto_core_ed25519_random (p) {
  const h = b4a.alloc(crypto_core_ed25519_UNIFORMBYTES)

  randombytes_buf(h)
  crypto_core_ed25519_from_uniform(p, h)
}

function crypto_core_ed25519_scalar_random (r) {
  do {
    randombytes_buf(r, crypto_core_ed25519_SCALARBYTES)
    r[crypto_core_ed25519_SCALARBYTES - 1] &= 0x1f
  } while (sc25519_is_canonical(r) == 0 ||
           sodium_is_zero(r, crypto_core_ed25519_SCALARBYTES));
}

function crypto_core_ed25519_scalar_invert (recip, s) {
  console.log(s)
  sc25519_invert(recip, s)
  console.log(recip)

  if (sodium_is_zero(s, crypto_core_ed25519_SCALARBYTES)) {
    throw new Error('Zero point')
  }
}

/* 2^252+27742317777372353535851937790883648493 */
const L = b4a.from([
  0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7,
  0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
])

function crypto_core_ed25519_scalar_negate (neg, s) {
  const t_ = b4a.alloc(crypto_core_ed25519_NONREDUCEDSCALARBYTES)
  const s_ = b4a.alloc(crypto_core_ed25519_NONREDUCEDSCALARBYTES)

  assert(crypto_core_ed25519_NONREDUCEDSCALARBYTES >=
          2 * crypto_core_ed25519_SCALARBYTES);

  t_.fill(0)
  s_.fill(0)

  t_.set(L.subarray(crypto_core_ed25519_SCALARBYTES, crypto_core_ed25519_SCALARBYTES))
  s_.set(s.subarray(0, crypto_core_ed25519_SCALARBYTES))

  sodium_sub(t_, s_, crypto_core_ed25519_SCALARBYTES)
  sc25519_reduce(t_)

  neg.set(t_.subarray(0 , crypto_core_ed25519_SCALARBYTES))
}

function crypto_core_ed25519_scalar_complement (comp, s) {
  const t_ = b4a.alloc(crypto_core_ed25519_NONREDUCEDSCALARBYTES)
  const s_ = b4a.alloc(crypto_core_ed25519_NONREDUCEDSCALARBYTES)

  assert(crypto_core_ed25519_NONREDUCEDSCALARBYTES >=
                  2 * crypto_core_ed25519_SCALARBYTES);

  t_.fill(0)
  s_.fill(0)
  t_[0]++;
  
  t_.set(L.subarray(crypto_core_ed25519_SCALARBYTES, crypto_core_ed25519_SCALARBYTES))
  s_.set(s.subarray(0, crypto_core_ed25519_SCALARBYTES))
  
  sodium_sub(t_, s_, crypto_core_ed25519_SCALARBYTES)
  sc25519_reduce(t_)

  comp.set(t_.subarray(0, crypto_core_ed25519_SCALARBYTES))
}

function crypto_core_ed25519_scalar_add (z, x, y) {
  const x_ = b4a.alloc(crypto_core_ed25519_NONREDUCEDSCALARBYTES)
  const y_ = b4a.alloc(crypto_core_ed25519_NONREDUCEDSCALARBYTES)

  x_.fill(0)
  y_.fill(0)

  x_.set(x.subarray(0, crypto_core_ed25519_SCALARBYTES))
  y_.set(y.subarray(0, crypto_core_ed25519_SCALARBYTES))

  sodium_add(x_, y_, crypto_core_ed25519_SCALARBYTES)
  crypto_core_ed25519_scalar_reduce(z, x_)
}

function crypto_core_ed25519_scalar_sub(z, x, y) {
  const yn = b4a.alloc(crypto_core_ed25519_SCALARBYTES)

  crypto_core_ed25519_scalar_negate(yn, y)
  crypto_core_ed25519_scalar_add(z, x, yn)
}

function crypto_core_ed25519_scalar_mul(z, x, y) {
  sc25519_mul(z, x, y)
}

function crypto_core_ed25519_scalar_reduce(r, s) {
  const t = b4a.alloc(crypto_core_ed25519_NONREDUCEDSCALARBYTES)

  t.set(s)

  sc25519_reduce(t)
  r.set(t.subarray(0, crypto_core_ed25519_SCALARBYTES))
  sodium_memzero(t)
}

function crypto_core_ed25519_scalar_is_canonical(s) {
  return sc25519_is_canonical(s)
}

const crypto_core_ed25519_BYTES = 32
const crypto_core_ed25519_UNIFORMBYTES = 32
const crypto_core_ed25519_SCALARBYTES = 32
const crypto_core_ed25519_NONREDUCEDSCALARBYTES = 64

module.exports = {
  crypto_core_ed25519_is_valid_point,
  crypto_core_ed25519_add,
  crypto_core_ed25519_sub,
  crypto_core_ed25519_from_uniform,
  crypto_core_ed25519_random,
  crypto_core_ed25519_scalar_random,
  crypto_core_ed25519_scalar_invert,
  crypto_core_ed25519_scalar_negate,
  crypto_core_ed25519_scalar_complement,
  crypto_core_ed25519_scalar_add,
  crypto_core_ed25519_scalar_sub,
  crypto_core_ed25519_scalar_mul,
  crypto_core_ed25519_scalar_reduce,
  crypto_core_ed25519_scalar_is_canonical,
  crypto_core_ed25519_BYTES,
  crypto_core_ed25519_UNIFORMBYTES,
  crypto_core_ed25519_SCALARBYTES,
  crypto_core_ed25519_NONREDUCEDSCALARBYTES
}
