/**
 * @file Converts argument to a value of type Number.
 * @version 2.0.0
 * @author Xotic750 <Xotic750@gmail.com>
 * @copyright  Xotic750
 * @license {@link <https://opensource.org/licenses/MIT> MIT}
 * @module to-number-x
 */

'use strict';

var cachedCtrs = require('cached-constructors-x');
var castNumber = cachedCtrs.Number;
var Rx = cachedCtrs.RegExp;
var toPrimitive = require('to-primitive-x');
var libTrim = require('trim-x');
var trim2016 = libTrim.trim2016;
var trim2018 = libTrim.trim2018;
var libParseInt = require('parse-int-x');
var $parseInt2016 = libParseInt.parseInt2016;
var $parseInt2018 = libParseInt.parseInt2018;
var pStrSlice = cachedCtrs.String.prototype.slice;
var NAN = require('nan-x');

var binaryRegex = /^0b[01]+$/i;
// Note that in IE 8, RegExp.prototype.test doesn't seem to exist: ie, "test" is
// an own property of regexes. wtf.
var test = binaryRegex.test;
var isBinary = function _isBinary(value) {
  return test.call(binaryRegex, value);
};

var octalRegex = /^0o[0-7]+$/i;
var isOctal = function _isOctal(value) {
  return test.call(octalRegex, value);
};

var nonWSregex2016 = new Rx('[\u0085\u200b\ufffe]', 'g');
var hasNonWS2016 = function _hasNonWS(value) {
  return test.call(nonWSregex2016, value);
};

var nonWSregex2018 = new Rx('[\u0085\u180e\u200b\ufffe]', 'g');
var hasNonWS2018 = function _hasNonWS(value) {
  return test.call(nonWSregex2018, value);
};

var invalidHexLiteral = /^[-+]0x[0-9a-f]+$/i;
var isInvalidHexLiteral = function _isInvalidHexLiteral(value) {
  return test.call(invalidHexLiteral, value);
};

var $toNumber2016 = function toNumber2016(argument) {
  var value = toPrimitive(argument, Number);
  if (typeof value === 'symbol') {
    throw new TypeError('Cannot convert a Symbol value to a number');
  }

  if (typeof value === 'string') {
    if (isBinary(value)) {
      return toNumber2016($parseInt2016(pStrSlice.call(value, 2), 2));
    }

    if (isOctal(value)) {
      return toNumber2016($parseInt2016(pStrSlice.call(value, 2), 8));
    }

    if (hasNonWS2016(value) || isInvalidHexLiteral(value)) {
      return NAN;
    }

    var trimmed = trim2016(value);
    if (trimmed !== value) {
      return toNumber2016(trimmed);
    }
  }

  return castNumber(value);
};

var $toNumber2018 = function toNumber2018(argument) {
  var value = toPrimitive(argument, Number);
  if (typeof value === 'symbol') {
    throw new TypeError('Cannot convert a Symbol value to a number');
  }

  if (typeof value === 'string') {
    if (isBinary(value)) {
      return toNumber2018($parseInt2018(pStrSlice.call(value, 2), 2));
    }

    if (isOctal(value)) {
      return toNumber2018($parseInt2018(pStrSlice.call(value, 2), 8));
    }

    if (hasNonWS2018(value) || isInvalidHexLiteral(value)) {
      return NAN;
    }

    var trimmed = trim2018(value);
    if (trimmed !== value) {
      return toNumber2018(trimmed);
    }
  }

  return castNumber(value);
};

module.exports = {
  /**
   * reference to toNumber2018.
   */
  toNumber: $toNumber2018,

  /**
   * This method converts argument to a value of type Number. (ES2016)

   * @param {*} argument - The argument to convert to a number.
   * @throws {TypeError} - If argument is a Symbol or not coercible.
   * @returns {*} The argument converted to a number.
   * @example
   * var toNumber = require('to-number-x').toNumber2016;
   *
   * toNumber('1'); // 1
   * toNumber(null); // 0
   * toNumber(true); // 1
   * toNumber('0o10'); // 8
   * toNumber('0b10'); // 2
   * toNumber('0xF'); // 16
   *
   * toNumber(' 1 '); // 1
   *
   * toNumber(Symbol('')) // TypeError
   * toNumber(Object.create(null)) // TypeError
   */
  toNumber2016: $toNumber2016,

  /**
   * This method converts argument to a value of type Number. (ES2018)

   * @param {*} argument - The argument to convert to a number.
   * @throws {TypeError} - If argument is a Symbol or not coercible.
   * @returns {*} The argument converted to a number.
   * @example
   * var toNumber = require('to-number-x').toNumber2018;
   *
   * toNumber('1'); // 1
   * toNumber(null); // 0
   * toNumber(true); // 1
   * toNumber('0o10'); // 8
   * toNumber('0b10'); // 2
   * toNumber('0xF'); // 16
   *
   * toNumber(' 1 '); // 1
   *
   * toNumber(Symbol('')) // TypeError
   * toNumber(Object.create(null)) // TypeError
   */
  toNumber2018: $toNumber2018
};
