import {Buffer} from 'buffer';
import BN from 'bn.js';
import {Md5Hash, SashaHash, Sha1Hash} from './hash';
import * as ellip from 'elliptic';
import {
  CommonDelimeterChar,
  Default_WordListArrayCount,
  KeyPair,
  NetworkType,
  Prefix_MainNetwork,
  Prefix_TestNetwork,
} from '../types';
import {SeedPhraseList} from './functions';

export function generateKeyPair(wordList: string[] = []): KeyPair {
  if (wordList.length == 0) wordList = SeedPhraseList();
  var privKey = generatePrivateKey(wordList);
  var pubKey = generatePublicKey(privKey);
  var walletAddress = generateWalletKey(pubKey, NetworkType.MainNet);
  return {
    PublicKey: pubKey,
    PrivateKey: privKey,
    WalletKey: walletAddress,
    Words: wordList,
  };
}

export function generateWalletKey(publicKey: string, network: NetworkType) {
  var pubCorsX = publicKey.substring(0, 64);
  var pubCorsY = publicKey.substring(64, publicKey.length);
  var pubKey = '0' + pubCorsX + '0' + pubCorsY;

  var pubCorsMain =
    (parseInt(pubCorsY.substring(pubCorsY.length - 1), 16) % 2 == 0
      ? '020'
      : '030') + pubCorsX;

  var networkByteStr = 10;
  var keyPrefix = Prefix_MainNetwork;

  if (network == NetworkType.TestNet) {
    networkByteStr = 20;
    keyPrefix = Prefix_TestNetwork;
  }

  var hashPubKeyStr = shrinkHex(SashaHash(SashaHash(pubCorsMain)), 22);

  var checkSumStr = shrinkHex(SashaHash(SashaHash(networkByteStr + pubKey)), 4);

  var walletAddress = bs58encode(
    BigInt(
      new BN(networkByteStr + hashPubKeyStr + checkSumStr, 16).toString(10),
    ),
    36,
  );

  return keyPrefix + walletAddress;
}

export function generatePublicKey(privateKey: string): string {
  var EC = ellip.ec;
  var ec = new EC('p256');
  var generatorPoint = ec
    .keyFromPrivate(privateKey)
    .ec.g.mul(new BN(privateKey, 16));
  return (
    generatorPoint.getX().toString(16) + generatorPoint.getY().toString(16)
  );
}

export function generatePrivateKey(wordList: string[]) {
  if (wordList.length != 16) return '0';

  var tmpWordList: string[] = [];
  var fullWordLine: string = '';
  for (var i = 0; i < Default_WordListArrayCount; i++) {
    if (i == 0) {
      fullWordLine = wordList[i] + CommonDelimeterChar;
    } else {
      fullWordLine = fullWordLine + wordList[i];
      if (Default_WordListArrayCount - 1 > i)
        fullWordLine = fullWordLine + CommonDelimeterChar;
    }
    tmpWordList[i] = Md5Hash(wordList[i]);
  }
  fullWordLine = SashaHash(
    SashaHash(fullWordLine, false, false) + CommonDelimeterChar + fullWordLine,
    false,
    false,
  );

  var hexResultStr: string = '';
  for (var i = 0; i < Default_WordListArrayCount; i++) {
    tmpWordList[i] = shrinkHex(
      Sha1Hash(fullWordLine + CommonDelimeterChar + tmpWordList[i]),
      2,
    );
    hexResultStr = hexResultStr + tmpWordList[i];
  }

  return hexResultStr;
}

export function signMessage(message: string, privateKey: string) {
  var EC = ellip.ec;
  var ec = new EC('p256');

  var t = SashaHash(message, false, false).substring(0, 64);
  var key = ec.keyFromPrivate(privateKey);
  var signature = key.sign(new BN(t, 16).toString(16));
  return {hash: t, signature: bytesToHex(Buffer.from(signature.toDER()))};
}

export function validateMessage(
  message: string,
  signature: string,
  publicKey: string,
) {
  var EC = ellip.ec;
  var ec = new EC('p256');
  var key = ec.keyFromPublic(
    {x: publicKey.substring(0, 64), y: publicKey.substring(64, 130)},
    'hex',
  );
  return key.verify(message, signature);
}

function shrinkHex(inputHexData: string, howManyByte: number): string {
  if (2 * howManyByte >= inputHexData.length) return inputHexData;

  var hexArray = hexToBytes(inputHexData);
  var xorValue = 0;
  for (var a = howManyByte; a < hexArray.length; a++) {
    xorValue = Uint8Array.from([xorValue, hexArray[a]]).reduce((a, b) => a ^ b);
  }

  var resultArray: Uint8Array = new Uint8Array(howManyByte);
  for (var x = 0; x < howManyByte; x++) {
    resultArray[x] = Uint8Array.from([hexArray[x], xorValue]).reduce(
      (a, b) => a ^ b,
    );
  }

  return bytesToHex(resultArray);
}

function hexToBytes(hex: string) {
  for (var bytes = [], c = 0; c < hex.length; c += 2)
    bytes.push(parseInt(hex.substr(c, 2), 16));
  return bytes;
}

function bytesToHex(bytes: Uint8Array) {
  for (var hex = [], i = 0; i < bytes.length; i++) {
    var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
    hex.push((current >>> 4).toString(16));
    hex.push((current & 0xf).toString(16));
  }
  return hex.join('');
}

const DefaultBase58AlphabetString =
  '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

function bs58encode(numberToShorten: bigint, resultSize: number = 32): string {
  var result: string[] = [];
  result.length = resultSize;

  var iAlphabetLength = DefaultBase58AlphabetString.length;
  var iAlphabetLength2 = BigInt(iAlphabetLength);

  var i = 0;
  while (numberToShorten >= 0 && result.length > i) {
    var lNumberRemainder = numberToShorten % iAlphabetLength2;
    numberToShorten /= iAlphabetLength2;
    result[result.length - 1 - i] =
      DefaultBase58AlphabetString[Number(lNumberRemainder)];
    i++;
  }

  return result.join('');
}
