const crc32 = require('buffer-crc32');
const sc_reduce32_module = require('./sc_reduce32');

module.exports = {
  mn_encode,
  mn_decode,
  sc_reduce32,
  get_languages,
};
class MnemonicError extends Error {}

function hexToUint8Array(e) {
  if (e.length % 2 != 0) throw 'Hex string has invalid length!';
  for (var t = new Uint8Array(e.length / 2), r = 0; r < e.length / 2; ++r)
    t[r] = parseInt(e.slice(2 * r, 2 * r + 2), 16);
  return t;
}

function Uint8ArrayToHex(e) {
  for (var t = [], r = 0; r < e.length; ++r)
    t.push(('0' + e[r].toString(16)).slice(-2));
  return t.join('');
}

function sc_reduce32(e) {
  var t = hexToUint8Array(e);
  if (32 !== t.length) throw 'Invalid input length';
  var r = sc_reduce32_module._malloc(32);
  sc_reduce32_module.HEAPU8.set(t, r),
    sc_reduce32_module.ccall('sc_reduce32', 'void', ['number'], [r]);
  var o = sc_reduce32_module.HEAPU8.subarray(r, r + 32);
  return sc_reduce32_module._free(r), Uint8ArrayToHex(o);
}
/*
 mnemonic.js : Converts between 4-byte aligned strings and a human-readable
 sequence of words. Uses 1626 common words taken from wikipedia article:
 http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry
 Originally written in python special for Electrum (lightweight Bitcoin client).
 This version has been reimplemented in javascript and placed in public domain.
 */

var mn_default_wordset = 'english';

function mn_get_checksum_index(words, prefix_len) {
  var trimmed_words = '';
  for (var i = 0; i < words.length; i++) {
    trimmed_words += words[i].slice(0, prefix_len);
  }
  var checksum = crc32.unsigned(trimmed_words);
  var index = checksum % words.length;
  return index;
}

function mn_encode(str, wordset_name) {
  'use strict';
  wordset_name = wordset_name || mn_default_wordset;
  var wordset = mn_words[wordset_name];
  var out = [];
  var n = wordset.words.length;
  for (var j = 0; j < str.length; j += 8) {
    str =
      str.slice(0, j) +
      mn_swap_endian_4byte(str.slice(j, j + 8)) +
      str.slice(j + 8);
  }
  for (var i = 0; i < str.length; i += 8) {
    var x = parseInt(str.substr(i, 8), 16);
    var w1 = x % n;
    var w2 = (Math.floor(x / n) + w1) % n;
    var w3 = (Math.floor(Math.floor(x / n) / n) + w2) % n;
    out = out.concat([wordset.words[w1], wordset.words[w2], wordset.words[w3]]);
  }
  if (wordset.prefix_len > 0) {
    out.push(out[mn_get_checksum_index(out, wordset.prefix_len)]);
  }
  return out.join(' ');
}

function mn_swap_endian_4byte(str) {
  'use strict';
  if (str.length !== 8)
    throw new MnemonicError('Invalid input length: ' + str.length);
  return str.slice(6, 8) + str.slice(4, 6) + str.slice(2, 4) + str.slice(0, 2);
}

function mn_decode(str, wordset_name) {
  'use strict';
  wordset_name = wordset_name || mn_default_wordset;
  var wordset = mn_words[wordset_name];
  var out = '';
  var n = wordset.words.length;
  var wlist = str.split(' ');
  var checksum_word = '';
  if (wlist.length < 12)
    throw new MnemonicError("You've entered too few words, please try again");
  if (
    (wordset.prefix_len === 0 && wlist.length % 3 !== 0) ||
    (wordset.prefix_len > 0 && wlist.length % 3 === 2)
  )
    throw new MnemonicError("You've entered too few words, please try again");
  if (wordset.prefix_len > 0 && wlist.length % 3 === 0)
    throw new MnemonicError(
      'You seem to be missing the last word in your private key, please try again'
    );
  if (wordset.prefix_len > 0) {
    // Pop checksum from mnemonic
    checksum_word = wlist.pop();
  }
  // Decode mnemonic
  for (var i = 0; i < wlist.length; i += 3) {
    var w1, w2, w3;
    if (wordset.prefix_len === 0) {
      w1 = wordset.words.indexOf(wlist[i]);
      w2 = wordset.words.indexOf(wlist[i + 1]);
      w3 = wordset.words.indexOf(wlist[i + 2]);
    } else {
      w1 = wordset.trunc_words.indexOf(wlist[i].slice(0, wordset.prefix_len));
      w2 = wordset.trunc_words.indexOf(
        wlist[i + 1].slice(0, wordset.prefix_len)
      );
      w3 = wordset.trunc_words.indexOf(
        wlist[i + 2].slice(0, wordset.prefix_len)
      );
    }
    if (w1 === -1 || w2 === -1 || w3 === -1) {
      throw new MnemonicError('invalid word in mnemonic');
    }
    var x = w1 + n * ((n - w1 + w2) % n) + n * n * ((n - w2 + w3) % n);
    if (x % n != w1)
      throw new MnemonicError(
        'Something went wrong when decoding your private key, please try again'
      );
    out += mn_swap_endian_4byte(('0000000' + x.toString(16)).slice(-8));
  }
  // Verify checksum
  if (wordset.prefix_len > 0) {
    var index = mn_get_checksum_index(wlist, wordset.prefix_len);
    var expected_checksum_word = wlist[index];
    if (
      expected_checksum_word.slice(0, wordset.prefix_len) !==
      checksum_word.slice(0, wordset.prefix_len)
    ) {
      throw new MnemonicError(
        'Your private key could not be verified, please try again'
      );
    }
  }
  return out;
}

var mn_words = {
  english: {
    prefix_len: 3,
    words: require('../mnemonic_languages/english'),
  },
  electrum: {
    prefix_len: 0,
    words: require('../mnemonic_languages/electrum'),
  },
  spanish: {
    prefix_len: 4,
    words: require('../mnemonic_languages/spanish'),
  },
  portuguese: {
    prefix_len: 4,
    words: require('../mnemonic_languages/portuguese'),
  },
  japanese: {
    prefix_len: 3,
    words: require('../mnemonic_languages/japanese'),
  },
};

function get_languages() {
  return Object.keys(mn_words);
}

for (var i in mn_words) {
  if (mn_words.hasOwnProperty(i)) {
    if (mn_words[i].prefix_len === 0) {
      continue;
    }
    mn_words[i].trunc_words = [];
    for (var j = 0; j < mn_words[i].words.length; ++j) {
      mn_words[i].trunc_words.push(
        mn_words[i].words[j].slice(0, mn_words[i].prefix_len)
      );
    }
  }
}
