'use strict';

var _typeof = require('@babel/runtime/helpers/typeof');
var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
var BN = require('bn.js');
var ethjsUtil = require('ethjs-util');
var minterjsUtil = require('minterjs-util');
var minterjsWallet = require('minterjs-wallet');

var BASE_COIN = {
  '0x01': 'BIP',
  '0x02': 'MNT'
};

/**
 * @param {number|string} chainId
 * @return {string}
 */
function normalizeChainId(chainId) {
  if (typeof chainId === 'string' || typeof chainId === 'number') {
    chainId = integerToHexString(chainId);
  }
  return chainId;
}

/**
 * @param {number|string} chainId
 * @param {string} coinSymbol
 * @return {boolean}
 */
function isBaseCoinSymbol(chainId, coinSymbol) {
  return BASE_COIN[normalizeChainId(chainId)] === coinSymbol;
}

/**
 * @param {number|string} chainId
 * @return {string|undefined}
 */
function getBaseCoinSymbol(chainId) {
  return BASE_COIN[normalizeChainId(chainId)];
}

/**
 * @param {number|string} coinIdOrSymbol
 * @return {boolean}
 */
function isCoinId(coinIdOrSymbol) {
  if (typeof coinIdOrSymbol === 'number') {
    return true;
  }
  if (typeof coinIdOrSymbol !== 'string') {
    return false;
  }
  return /^[0-9]+$/.test(coinIdOrSymbol);
}

/**
 * @param {string} coin
 * @param {object} [options]
 * @param {boolean} [options.allowVersion = true]
 * @param {boolean} [options.allowLP = true]
 * @return {boolean}
 */
function isCoinSymbol(coin) {
  var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
    _ref$allowVersion = _ref.allowVersion,
    allowVersion = _ref$allowVersion === void 0 ? true : _ref$allowVersion,
    _ref$allowLP = _ref.allowLP,
    allowLP = _ref$allowLP === void 0 ? true : _ref$allowLP;
  if (typeof coin !== 'string') {
    return false;
  }
  var _coin$split = coin.split('-'),
    _coin$split2 = _slicedToArray(_coin$split, 3),
    ticker = _coin$split2[0],
    version = _coin$split2[1],
    invalidPart = _coin$split2[2];
  if (invalidPart !== undefined) {
    // console.debug('invalid part found, e.g. "ABC-12-34"')
    return false;
  }
  // validate version
  if (!allowVersion && version !== undefined) {
    // console.debug('version is not allowed');
    return false;
  }
  if ((version === null || version === void 0 ? void 0 : version.length) === 0) {
    // console.debug('empty version, e.g. "ABC-"');
    return false;
  }
  if ((version === null || version === void 0 ? void 0 : version.length) > 0 && !/^\d+$/.test(version)) {
    // console.debug('only digits in version');
    return false;
  }
  // validate LP
  var isLP = ticker === 'LP' && (version === null || version === void 0 ? void 0 : version.length) > 0;
  if (isLP) {
    return allowLP;
  }
  // validate ticker
  if (!/[A-Z]/.test(ticker)) {
    // console.debug('ticker should have at least one letter');
    return false;
  }
  // only letters and digits in ticker
  return /^[A-Z0-9]{3,10}$/.test(ticker);
}

/**
 * @param {any} value
 */
function isValidNumber(value) {
  var invalid = typeof value !== 'number' && typeof value !== 'string' || typeof value === 'string' && value.length === 0;
  return !invalid;
}

/**
 * @param {number|string|ByteArray} num
 * @return {string}
 */
function integerToHexString(num) {
  num = toInteger(num);
  // handle exponential values
  num = new minterjsUtil.Big(num).toFixed();
  // convert to hex
  var hexNum = new BN(num, 10).toString(16);
  return "0x".concat(ethjsUtil.padToEven(hexNum));
}

/**
 * @param {number|string|ByteArray} num
 * @return {string}
 */
function toInteger(num) {
  if (typeof num === 'number') {
    return num.toString();
  }
  if (num !== undefined && num !== null && num.length > 0) {
    // handle hex prefixed string
    if (typeof num === 'string' && ethjsUtil.isHexPrefixed(num)) {
      return bufferToInteger(num);
    }
    // handle arrays
    if (typeof num !== 'string') {
      return bufferToInteger(num);
    }
  }
  num = Number.parseInt(num, 10);
  return Number.isNaN(num) ? '' : num.toString();
}

/**
 * @param {ByteArray} buf
 * @return {string}
 */
function bufferToInteger(buf) {
  buf = bufferFromBytes(buf);
  return new BN(buf, 16).toString(10);
}

/**
 * @param {ByteArray} buf
 * @return {boolean|null}
 */
function bufferToBoolean(buf) {
  buf = bufferFromBytes(buf);
  if (buf.toString('hex') === '01') {
    return true;
  }
  if (buf.toString('hex') === '') {
    return false;
  }

  // eslint-disable-next-line unicorn/no-null
  return null;
}

/**
 * @typedef {Buffer|Array|string|number|null|undefined|BN} BufferCapable
 */

/**
 * @param {BufferCapable} value
 * @returns {string}
 */
function dataToInteger(value) {
  return bufferToInteger(minterjsUtil.toBuffer(value));
}

/**
 * @param {BufferCapable} value
 * @returns {string}
 */
function dataPipToAmount(value) {
  return minterjsUtil.convertFromPip(bufferToInteger(minterjsUtil.toBuffer(value)));
}

/**
 * @param {BufferCapable} value
 * @returns {string}
 */
function dataToAddress(value) {
  // use zero address
  // if (!value || value?.length === 0) {
  //     value = Buffer.alloc(20, 0);
  // }
  return minterjsUtil.addressToString(value);
}

/**
 * @param {BufferCapable} value
 * @returns {string}
 */
function dataToPublicKey(value) {
  // use zero address
  // if (!value || value?.length === 0) {
  //     value = Buffer.alloc(32, 0);
  // }
  return minterjsUtil.publicToString(value);
}

/**
 * @param {ByteArray} bytes
 * @return {Buffer}
 */
function bufferFromBytes(bytes) {
  if (bytes.length === undefined) {
    throw new Error('Invalid value passed as ByteArray, it should be Buffer, Uint8Array or hex string');
  }
  // string to Buffer
  if (typeof bytes === 'string') {
    bytes = bytes.replace('0x', '');
    return Buffer.from(bytes, 'hex');
  }
  // Uint8Array to Buffer
  if (!Buffer.isBuffer(bytes)) {
    return Buffer.from(bytes);
  }

  // it is Buffer already
  return bytes;
}

/**
 * @param {object} obj
 */
function proxyNestedTxData(obj) {
  addTxDataFields(obj);

  // proxy TxData
  obj.raw = obj.txData.raw;
  obj.serialize = obj.txData.serialize;
  obj.serializeToString = obj.txData.serializeToString;
}

/**
 * @param {object} txData
 */
function addTxDataFields(txData) {
  Object.defineProperty(txData, 'fields', {
    get: function get() {
      var fields = {};
      txData.txData._fields.forEach(function (key) {
        if (Array.isArray(txData[key])) {
          // cast multisend items to fields
          fields[key] = txData[key].map(function (item) {
            return item.fields || item;
          });
        } else {
          fields[key] = txData[key];
        }
      });
      return fields;
    },
    enumerable: true
  });
}

/**
 * @param {string} value
 * @param {string} fieldName
 */
function validateAddress(value, fieldName) {
  validateNotEmpty(value, fieldName);
  if (typeof value === 'string' && !minterjsUtil.isValidAddress(value)) {
    throw new Error("Field `".concat(fieldName, "` is invalid address"));
  }
}

/**
 * @param {string} value
 * @param {string} fieldName
 */
function validatePublicKey(value, fieldName) {
  validateNotEmpty(value, fieldName);
  if (typeof value === 'string' && !minterjsUtil.isValidPublicKeyString(value)) {
    throw new Error("Field `".concat(fieldName, "` is invalid public key"));
  }
}

/**
 * @param {string} value
 * @param {string} fieldName
 */
function validateCheck(value, fieldName) {
  validateNotEmpty(value, fieldName);
  if (typeof value === 'string' && !minterjsUtil.isValidCheck(value)) {
    throw new Error("Field `".concat(fieldName, "` is invalid check string"));
  }
}

/**
 * @param {number|string} value
 * @param {string} fieldName
 */
function validateAmount(value, fieldName) {
  validateNotEmpty(value, fieldName);
  if (typeof value === 'string' || typeof value === 'number') {
    var valueBig;
    try {
      valueBig = minterjsUtil.numberToBig(value);
    } catch (error) {
      throw new Error("Field `".concat(fieldName, "` is invalid number"));
    }
    if (valueBig && valueBig.lt(0)) {
      throw new Error("Field `".concat(fieldName, "` has negative amount"));
    }
  }
}

/**
 * @param {number|string} maxSupply
 * @param {number|string} initialAmount
 */
function validateMaxSupply(maxSupply, initialAmount) {
  validateAmount(maxSupply, 'maxSupply');
  if (maxSupply > minterjsUtil.COIN_MAX_MAX_SUPPLY || maxSupply < minterjsUtil.COIN_MIN_MAX_SUPPLY) {
    throw new Error("Field `maxSupply` should be between ".concat(minterjsUtil.COIN_MIN_MAX_SUPPLY, " and ").concat(minterjsUtil.COIN_MAX_MAX_SUPPLY));
  }
  if (Number(initialAmount) > Number(maxSupply)) {
    throw new Error('Field `initialAmount` should be less or equal of `maxSupply`');
  }
}

/**
 * @param {number|string} origValue
 * @param {string} fieldName
 */
function validateUint(origValue, fieldName) {
  validateNotEmpty(origValue, fieldName);
  var value = Number(origValue);
  if (Number.isNaN(value)) {
    throw new TypeError("Field `".concat(fieldName, "` is not a number. Received: ").concat(origValue));
  }
  if (value < 0) {
    throw new Error("Field `".concat(fieldName, "` should be positive integer. Received: ").concat(value));
  }
  if (Math.round(value) !== value) {
    throw new Error("Field `".concat(fieldName, "` should be integer, decimal given"));
  }
}

/**
 * @param {number|string} origValue
 * @param {string} fieldName
 */
function validateUintArray(origValue, fieldName) {
  if (!Array.isArray(origValue)) {
    throw new TypeError("Field `".concat(fieldName, "` is not an array"));
  }
  origValue.forEach(function (coin, index) {
    try {
      validateUint(coin, fieldName);
    } catch (error) {
      // update error message
      throw new Error(error.message.replace("`".concat(fieldName, "`"), "`".concat(fieldName, "` contain invalid item at index: ").concat(index, ", it ")));
    }
  });
}

/**
 * Only validates base coin ticker. Throws on LP-* tokens and ARCHIVED-* coins with version number.
 * @param {string} value
 * @param {string} fieldName
 */
function validateTicker(value, fieldName) {
  validateNotEmpty(value, fieldName);
  if (typeof value === 'string' && !isCoinSymbol(value, {
    allowVersion: false,
    allowLP: false
  })) {
    throw new Error("Field `".concat(fieldName, "` is invalid coin ticker string"));
  }
}

/**
 * @param {any} value
 * @param {string} fieldName
 */
function validateNotEmpty(value, fieldName) {
  if (value === undefined) {
    throw new TypeError("Field `".concat(fieldName, "` is undefined"));
  }
  if (value === null) {
    throw new Error("Field `".concat(fieldName, "` is null"));
  }
  if (value === false) {
    throw new Error("Field `".concat(fieldName, "` is false"));
  }
  if (value === '') {
    throw new Error("Field `".concat(fieldName, "` is empty string"));
  }
}

/**
 * @param {boolean} value
 * @param {string} fieldName
 */
function validateBoolean(value, fieldName) {
  if (typeof value !== 'boolean') {
    throw new TypeError("Field `".concat(fieldName, "` should be boolean, ").concat(_typeof(value), " given"));
  }
}

/**
 * @param {string} seedPhrase
 * @return {string}
 */
function getPrivateKeyFromSeedPhrase(seedPhrase) {
  return minterjsWallet.walletFromMnemonic(seedPhrase).getPrivateKeyString();
}

/**
 * @param {string} seedPhrase
 * @return {Promise<string>}
 */
function getPrivateKeyFromSeedPhraseAsync(seedPhrase) {
  return minterjsWallet.walletFromMnemonicAsync(seedPhrase).then(function (wallet) {
    return wallet.getPrivateKeyString();
  });
}

/**
 * Promisify setTimeout
 * @param {number} time - milliseconds
 * @return {Promise}
 */
function wait(time) {
  return new Promise(function (resolve) {
    setTimeout(resolve, time);
  });
}

exports.addTxDataFields = addTxDataFields;
exports.bufferFromBytes = bufferFromBytes;
exports.bufferToBoolean = bufferToBoolean;
exports.bufferToInteger = bufferToInteger;
exports.dataPipToAmount = dataPipToAmount;
exports.dataToAddress = dataToAddress;
exports.dataToInteger = dataToInteger;
exports.dataToPublicKey = dataToPublicKey;
exports.getBaseCoinSymbol = getBaseCoinSymbol;
exports.getPrivateKeyFromSeedPhrase = getPrivateKeyFromSeedPhrase;
exports.getPrivateKeyFromSeedPhraseAsync = getPrivateKeyFromSeedPhraseAsync;
exports.integerToHexString = integerToHexString;
exports.isBaseCoinSymbol = isBaseCoinSymbol;
exports.isCoinId = isCoinId;
exports.isCoinSymbol = isCoinSymbol;
exports.isValidNumber = isValidNumber;
exports.proxyNestedTxData = proxyNestedTxData;
exports.toInteger = toInteger;
exports.validateAddress = validateAddress;
exports.validateAmount = validateAmount;
exports.validateBoolean = validateBoolean;
exports.validateCheck = validateCheck;
exports.validateMaxSupply = validateMaxSupply;
exports.validatePublicKey = validatePublicKey;
exports.validateTicker = validateTicker;
exports.validateUint = validateUint;
exports.validateUintArray = validateUintArray;
exports.wait = wait;
