/*!
 * @riil-frontend/utils.js v0.1.7
 * (c) 2020 cheetah
 * Released under the MIT License.
 */
import validator from 'validator';
import moment from 'moment';
import { fromJS } from 'immutable';
import CryptoJS from 'crypto-js';

/**
 * 递归的方式找到当前节点下的所有的子节点
 * @param current 当前节点
 * @param opt 配置
 * @param array 原数组
 * @returns {Array}
 * @access private
 */
function getChildren(current, opt, array) {
  const id = current[opt.keyFiled];
  const childEle = [];
  array.map(function(item) {
    if (item[opt.parentFiled] === id) {
      childEle.push(item);
    }
  });
  childEle.length && childEle.map(function(ele) {
    ele[opt.childFiled] = getChildren(ele, opt, array);
  });
  return childEle;
}

/**
 *
 * 将平行数组转换为树形结构
 * @category 数组
 * @function arrayToTree
 * @param array 数据集合
 * @returns  {object} 树形结构
 * @static
 * @author libo
 * @param {Object} option - 参数配置.
 * @param {string} option.parentFiled - 父级指向字段标识，默认值'parent'。
 * @param {string} option.keyFiled - 唯一标识字段标识,默认值'id'。
 * @param {string} option.childFiled - 树形结构的子字段，默认值'children'。
 * @param {string} option.returnType - 返回数据类型  支持tree和object，默认值'tree'。
 * @param {string} option.isDeep - 是否深拷贝，默认值false。
 *
 * 等价 'parent'
 * @example example 01 默认使用
 * --------------------------------------------------------------------------
 * import { arrayToTree } from "@riil-frontend/utils";
 * const array = [
 *     { id: 1, value: 1, parent: null },
 *     { id: 2, value: 2, parent: 1 }
 * ];
 * arrayToTree(array);
 * // result
 * [
 *   {
 *     "id": 1,
 *     "value": 1,
 *     "parent": null,
 *     "children": [
 *       {
 *         "id": 2,
 *         "value": 2,
 *         "parent": 1,
 *         "children": []
 *       }
 *     ]
 *   }
 * ]
 *
 * @example example 02 返回对象格式 便捷获取某个节点数据的及所有子数据
 * --------------------------------------------------------------------------
 * const array = [
 *     { id: 1, value: 1, parent: null },
 *     { id: 2, value: 2, parent: 1 }
 * ];
 * arrayToTree(array,{returnType:'object'});
 *
 * // result
 * {
 *   "1": {
 *     "id": 1,
 *     "value": 1,
 *     "parent": null,
 *     "children": [
 *       {
 *         "id": 2,
 *         "value": 2,
 *         "parent": 1,
 *         "children": []
 *       }
 *     ]
 *   },
 *   "2": {
 *     "id": 2,
 *     "value": 2,
 *     "parent": 1,
 *     "children": []
 *   }
 * }
 *
 */
const arrayToTree = (array, option) => {
  if (!array || !array.length) return option && option.returnType === 'object' ? {} : [];
  let opt = { parentFiled: 'parent', keyFiled: 'id', childFiled: 'children', returnType: 'tree' };
  typeof option === 'string' ? (opt.parentFiled = option) : (opt = { ...opt, ...option });
  const temp = {};
  const first = [];
  array.map((obj) => (temp[obj[opt.keyFiled]] = obj));
  // 查处所有的一级菜单
  array.map((current) => {
    const parent = current[opt.parentFiled];
    // parent字段不存在 或者paren字段不存在父级
    if (!parent || !temp[parent]) {
      first.push(current);
    }
  });
  // 根据一级菜单获取所有孩子节点
  first &&
    first.map((firstChild) => {
      firstChild[opt.childFiled] = getChildren(firstChild, opt, array);
    });
  return opt.returnType === 'object' ? temp : first;
};

/**
 * ---
 *  对象数组动态排序
 * @param {*} property 对象key值
 * @param sortOrder 1正序，-1倒序
 * @static
 * @returns {boolean}
 * @author libo
 * @function dynamicSort
 * @example  01 对象数组排序
 * --------------------------------------------------------------------------
 *  import { dynamicSort } from "@riil-frontend/utils";
 *  var People = [
 *      {Name: "Name", Surname: "Surname"},
 *      {Name:"AAA", Surname:"ZZZ"},
 *      {Name: "Name", Surname: "AAA"}
 *  ];
 *
 *  People.sort(dynamicSort("Name"));
 *  People.sort(dynamicSort("Surname"));
 *  People.sort(dynamicSort("Surname",-1));//倒序
 *
 */
const dynamicSort = (property, sortOrder = 1) => {
  return function(a, b) {
    const result = a[property] < b[property] ? -1 : a[property] > b[property] ? 1 : 0;
    return result * sortOrder;
  };
};

/**
 * 递归函数，返回符合集合
 * @category 数组
 * @function recursionGet
 * @returns  {array} 符合条件的数组
 * @static
 * @author hht
 * @param {string}} value 查找的值.
 * @param {array} data 所有树节点数据。
 *
 * @example example 01 匹配子节点
 * --------------------------------------------------------------------------
 * import { recursionGet } from "@riil-frontend/utils";
 * const array = [
 *  {
 *    label: '网络设备',
 *    id: 'network',
 *    icon: 'network',
 *    children: [
 *      {
 *        label: '交换机',
 *        id: 'switch',
 *        icon: 'switch',
 *        children: null,
 *      },
 *      {
 *        label: '锐捷交换机',
 *        id: 'ruijieSwitch',
 *        icon: 'ruijieSwitch',
 *        children: null,
 *      },
 *      {
 *        label: '路由器',
 *        id: 'router',
 *        icon: 'router',
 *        children: null,
 *      },
 *      {
 *        label: '锐捷路由器',
 *        id: 'ruijieRouter',
 *        icon: 'ruijieRouter',
 *        children: null,
 *      },
 *    ],
 *  },
 *  {
 *    label: '操作系统',
 *    id: 'system',
 *    icon: 'system',
 *    children: [
 *      {
 *        label: 'Linux',
 *        id: 'Linux',
 *        icon: 'Linux',
 *        children: null,
 *      },
 *      {
 *        label: 'Windows',
 *        id: 'Windows',
 *        icon: 'Windows',
 *        children: null,
 *      },
 *    ],
 *  },
 * ];
 * recursionGet('Linux',array);
 * // result
 * ['Linux']
 *
 * @example example 02 无匹配
 * --------------------------------------------------------------------------
 * const array = [
 *     { id: 1, label: 1, children: null },
 *     { id: 2, label: 2, children: 1 }
 * ];
 * recursionGet("Linux",array);
 *
 * // result
 * []
 *
 */
const recursionGet = (value, data) => {
  if (!data || !Array.isArray(data)) return [];
  const keys = [];
  const loop = (arr) =>
    arr.forEach((item) => {
      if (item?.label && item.label.indexOf(value) > -1) {
        keys.push(item.id);
        return;
      }
      if (item?.children?.length) {
        loop(item.children);
      }
    });
  // 递归遍历
  loop(data);
  return keys;
};

/**
 * 对数组进行分组，分组后的对象
 * @category 数组
 * @function arrayGroupBy
 * @returns  {array} 符合条件的数组
 * @static
 * @author hht
 * @param {string}} key 分组属性.
 * @param {array} data 数组数据。
 *
 * @example example 01 匹配子节点
 * --------------------------------------------------------------------------
 * import { arrayGroupBy } from "@riil-frontend/utils";
 * const array = [
 *  {
 *    label: '网络设备',
 *    id: 'network',
 *    icon: 'network',
 *    children: [
 *      {
 *        label: '交换机',
 *        id: 'switch',
 *        icon: 'switch',
 *        children: null,
 *      },
 *      {
 *        label: '锐捷交换机',
 *        id: 'ruijieSwitch',
 *        icon: 'ruijieSwitch',
 *        children: null,
 *      },
 *      {
 *        label: '路由器',
 *        id: 'router',
 *        icon: 'router',
 *        children: null,
 *      },
 *      {
 *        label: '锐捷路由器',
 *        id: 'ruijieRouter',
 *        icon: 'ruijieRouter',
 *        children: null,
 *      },
 *    ],
 *  },
 *  {
 *    label: '操作系统',
 *    id: 'system',
 *    icon: 'system',
 *    children: [
 *      {
 *        label: 'Linux',
 *        id: 'Linux',
 *        icon: 'Linux',
 *        children: null,
 *      },
 *      {
 *        label: 'Windows',
 *        id: 'Windows',
 *        icon: 'Windows',
 *        children: null,
 *      },
 *    ],
 *  },
 * ];
 * arrayGroupBy('Linux',array);
 * // result
 * ['Linux']
 *
 * @example example 02 无匹配
 * --------------------------------------------------------------------------
 * const array = [
 *     { id: 1, label: 1, children: null },
 *     { id: 2, label: 2, children: 1 }
 * ];
 * arrayGroupBy("Linux",array);
 *
 * // result
 * []
 *
 */
const arrayGroupBy = (data, key) => {
  if (!data || !Array.isArray(data)) return [];
  return data.reduce((acc, cur) => {
    (acc[cur[key]] = acc[cur[key]] || []).push(cur);
    return acc;
  }, {});
};

/**
 ***********************************************************
 *
 *   校验相关公共方法
 *   使用第三方校验库 validator 详细使用参考 [官方文档](https://github.com/validatorjs/validator.js)
 *   @see {@link  https://github.com/validatorjs/validator.js|查看官方文档}
 *
 ********************************************************** */

/**
 * ---
 * 数字类校验，系统限制小数点前9位，小数点后两位，正数<br/>
 * 详单功能Table高级过滤器-比较过滤输入框校验 isPassForSpecification
 *  *
 * @returns boolean 校验结果
 * @function isPassForSpecification
 * @static

 * @returns {boolean}
 * @example 01 校验通过
 * --------------------------------------------------------------------------
 * import { validator } from '@riil-frontend/utils';
 * validator.isPassForSpecification('1564.33');
 *
 * // result
 * @example 02 校验不通过
 * --------------------------------------------------------------------------
 * import { validator } from '@riil-frontend/utils';
 * validator.isPassForSpecification('aaaa');
 *
 * // result
 * false
 */
const isPassForSpecification = (inputValue) => {
  if (inputValue === '.' || inputValue === '') {
    return true;
  }
  if (validator.isFloat(inputValue) && !inputValue.includes('+') && !inputValue.includes('-')) {
    let integerSuccess = true;
    let decimalsSuccess = true;
    const [integerStr, decimalsStr] = inputValue.split('.');
    if (integerStr && integerStr.length > 9) {
      integerSuccess = false;
    }
    if (decimalsStr && decimalsStr.length > 2) {
      decimalsSuccess = false;
    }
    return integerSuccess && decimalsSuccess;
  }
  return false;
};

/**
 * 端口验证
 *
 * @param rule  ant form rule
 * @param value 端口值可以为数组，将分别校验
 * @param callback ant form callback
 * @param source 要验证的对象（必填）。
 * @param options 一个对象，描述验证的处理选项（可选）。
 * @function validatePort
 * @static
 * @returns {*}
 */
const validatePort = (rule, value, callback, source, options) => {
  const values = Array.isArray(value) ? value : [value];
  for (let i = 0; i < values.length; i++) {
    const item = values[i];
    if (!isNull(item) && !validator.isPort(item)) {
      // 布尔值，是否禁止显示有关无效值的内部警告。
      options.suppressWarning = true;
      callback('不是合法的端口');
      return;
    }
  }
  return callback();
};

/**
 * 去除url中的空格
 * @param value url
 * @function removeUrlSpace
 * @static

 * @returns {*|void|string}
 */
const removeUrlSpace = (value) => value.replace(/\s*/g, '');

/**
 * http Urls验证
 * @param rule
 * @param value
 * @param callback
 * @returns {*}
 * @static

 * @function httpValidate
 */
const httpValidate = (rule, value, callback) => {
  let errorString = '';
  if (value.length > 10) {
    return callback(`最多支持10个地址，当前${value.length}个`);
  }
  if (value.length === 0) {
    return callback('请输入地址');
  }
  for (let i = 0; i < value.length; i++) {
    let item = value[i];
    item = removeUrlSpace(item);
    if (!item.startsWith('http://')) {
      errorString = '包含不符合http开头的url';
    } else if (validator.isURL(item)) {
      if (item.length > 256) {
        errorString = 'url地址长度，不能超过256个汉字或字符';
      }
    } else {
      errorString = '不符合url格式';
    }
    if (errorString != '') {
      break;
    }
  }
  if (errorString != '') {
    return callback(errorString);
  }
  return callback();
};
/**
 * https Urls验证
 * @param rule
 * @param value
 * @param callback
 * @function httpsValidate
 * @static

 * @returns {*}
 */
const httpsValidate = (rule, value, callback) => {
  let errorString = '';
  if (value.length > 10) {
    return callback(`最多支持10个地址，当前${value.length}个`);
  }
  if (value.length === 0) {
    return callback('请输入地址');
  }
  for (let i = 0; i < value.length; i++) {
    let item = value[i];
    item = removeUrlSpace(item);
    if (!item.startsWith('https://')) {
      errorString = '包含不符合https开头的url';
    } else if (validator.isURL(item)) {
      if (item.length > 256) {
        errorString = 'url地址长度，不能超过256个汉字或字符';
      }
    } else {
      errorString = '不符合url格式';
    }
    if (errorString !== '') {
      break;
    }
  }
  if (errorString !== '') {
    return callback(errorString);
  }
  return callback();
};
/**
 * ip+端口验证
 * @param rule
 * @param value
 * @param callback
 * @returns {*}
 * @static

 * @function tcpValidate
 */
const tcpValidate = (rule, value, callback) => {
  if (value.length > 10) {
    return callback(`最多支持10个地址，当前${value.length}个`);
  }
  if (value.length === 0) {
    return callback('请输入地址');
  }
  let isPass = true;
  const pattIp = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\:([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])$/;
  for (let i = 0; i < value.length; i++) {
    const item = value[i];
    if (!pattIp.test(item)) {
      isPass = false;
      break;
    }
  }
  if (!isPass) {
    return callback('包含不符合IP+端口的地址，且端口范围应为0-65535');
  }
  return callback();
};

/**
 * 录像文本关键字验证
 * @param rule
 * @param value
 * @param callback
 * @returns {*}
 * @function successContentValidate
 * @static
 */
const successContentValidate = (rule, value, callback) => {
  let errorString = '';
  if (value.length > 10) {
    return callback(`最多支持10组，当前${value.length}组`);
  }
  for (let i = 0; i < value.length; i++) {
    const item = value[i];
    if (item.length > 256) {
      errorString = '字符长度不能超过256个汉字或字符';
    }
    if (errorString !== '') {
      break;
    }
  }
  if (errorString !== '') {
    return callback(errorString);
  }
  return callback();
};

/**
 * 处理js number.toFixed方法的bug
 * @param {*} num
 * @param {*} d
 * @access private
 */
const toFixed = (num, d) => {
  let s = `${num}`;
  if (!d) d = 0;
  if (s.indexOf('.') == -1) s += '.';
  s += new Array(d + 1).join('0');
  const regex = new RegExp(`^(-|\\+)?(\\d+(\\.\\d{0,${d + 1}})?)\\d*$`);
  if (regex.test(s)) {
    let s = `0${RegExp.$2}`;
    const pm = RegExp.$1;
    let a = RegExp.$3.length;
    let b = true;
    if (a == d + 2) {
      a = s.match(/\d/g);
      if (parseInt(a[a.length - 1]) > 4) {
        for (let i = a.length - 2; i >= 0; i--) {
          a[i] = parseInt(a[i]) + 1;
          if (a[i] == 10) {
            a[i] = 0;
            b = i != 1;
          } else break;
        }
      }
      s = a.join('').replace(new RegExp(`(\\d+)(\\d{${d}})\\d$`), '$1.$2');
    }
    if (b) s = s.substr(1);
    regex.lastIndex = 0;
    return (pm + s).replace(/\.$/, '');
  }
  return `${num}`;
};
/**
 *
 * 数字格式化，保留N位小数
 * @param x 数值
 * @param n 保留位数
 * @function toDecimal
 * @returns {number}
 */
const toDecimal = (x, n = 2) => {
  let f = parseFloat(x);
  if (isNaN(f)) {
    return x;
  }
  f = parseFloat(toFixed(x, parseInt(n)));
  return f;
};

/**
 * 对外公开单位常量，包含单位名称以及单位体量
 * 同类型单位标记为x_units:[a,b,...],数组对象为对应单位对象名称，按单位体量从小到大排序
 * 单位对象标记为x:{key:'x',name:'X',carryIndex:i,decimal:j,type:'x_units'}其中：
 * key:单位标示符，和单位对象同名
 * name:单位显示值
 * carryIndex:同组单位体量指数固有系数，应为连续整数
 * carry:单位体量指数常量，可理解为单位进制转换为10的decimal次幂
 * type：单位所属同类单位组对象名称
 * @constant 单位_Units
 */
const Units = {
  // 秒，毫秒
  s_units: { keys: ['ms', 's'], carry: 1000 },
  ms: { key: 'ms', name: 'ms', carryIndex: 0, type: 's_units' },
  s: { key: 's', name: 's', carryIndex: 1, type: 's_units' },
  // 流量
  b_units: { keys: ['b', 'kb', 'mb', 'gb', 'tb'], carry: 1000 },
  b: { key: 'b', name: 'b', carryIndex: 0, type: 'b_units' },
  kb: { key: 'kb', name: 'Kb', carryIndex: 1, type: 'b_units' },
  mb: { key: 'mb', name: 'Mb', carryIndex: 2, type: 'b_units' },
  gb: { key: 'gb', name: 'Gb', carryIndex: 3, type: 'b_units' },
  tb: { key: 'tb', name: 'Tb', carryIndex: 4, type: 'b_units' },

  // 流量B
  B_units: { keys: ['B', 'KB', 'MB', 'GB', 'TB'], carry: 1000 },
  B: { key: 'B', name: 'B', carryIndex: 0, type: 'B_units' },
  KB: { key: 'KB', name: 'KB', carryIndex: 1, type: 'B_units' },
  MB: { key: 'MB', name: 'MB', carryIndex: 2, type: 'B_units' },
  GB: { key: 'GB', name: 'GB', carryIndex: 3, type: 'B_units' },
  TB: { key: 'TB', name: 'TB', carryIndex: 4, type: 'B_units' },

  // 流速
  bps_units: { keys: ['bps', 'kbps', 'mbps', 'gbps', 'tbps'], carry: 1000 },
  bps: { key: 'bps', name: 'bps', carryIndex: 0, type: 'bps_units' },
  kbps: { key: 'kbps', name: 'Kbps', carryIndex: 1, type: 'bps_units' },
  mbps: { key: 'mbps', name: 'Mbps', carryIndex: 2, type: 'bps_units' },
  gbps: { key: 'gbps', name: 'Gbps', carryIndex: 3, type: 'bps_units' },
  tbps: { key: 'tbps', name: 'Tbps', carryIndex: 4, type: 'bps_units' },

  // 流速B
  Bps_units: { keys: ['Bps', 'KBps', 'MBps', 'GBps', 'TBps'], carry: 1000 },
  Bps: { key: 'Bps', name: 'Bps', carryIndex: 0, type: 'Bps_units' },
  KBps: { key: 'KBps', name: 'KBps', carryIndex: 1, type: 'Bps_units' },
  MBps: { key: 'MBps', name: 'MBps', carryIndex: 2, type: 'Bps_units' },
  GBps: { key: 'GBps', name: 'GBps', carryIndex: 3, type: 'Bps_units' },
  TBps: { key: 'TBps', name: 'TBps', carryIndex: 4, type: 'Bps_units' },
  // 百分号数字
  percent_units: { keys: ['num', '%'], carry: 100 },
  num: { key: 'num', name: 'num', carryIndex: 0, type: 'percent_units' },
  '%': { key: '%', name: '%', carryIndex: -1, type: 'percent_units' },
  // 人，万人
  p_units: { keys: ['人', '万人'], carry: 10000 },
  人: { key: '人', name: '人', carryIndex: 0, type: 'p_units' },
  万人: { key: '万人', name: '万人', carryIndex: 1, type: 'p_units' },
  // 个，万个
  a_units: { keys: ['个', '万个'], carry: 10000 },
  个: { key: '个', name: '个', carryIndex: 0, type: 'a_units' },
  万个: { key: '万个', name: '万个', carryIndex: 1, type: 'a_units' },
  // 次，万次
  n_units: { keys: ['次', '万次'], carry: 10000 },
  次: { key: '次', name: '次', carryIndex: 0, type: 'n_units' },
  万次: { key: '万次', name: '万次', carryIndex: 1, type: 'n_units' },
  // 时间戳
  ts: { key: 'ts', name: '时间戳' },
  // 空值
  empty: '-',
  // 默认时间转换模板
  dateFormat_default: 'YYYY-MM-DD HH:mm',
};

/**
 ***********************************************************************************************************************
 *
 *                                     单位转换的公共方法
 *
 ********************************************************************************************************************** */
/**
 * @typedef {'ms'|'s'|'b'|'kb'|'mb'|'gb'|'tb'|'bps'|'kbps'|'mbps'|'gbps'|'tbps'|'人'|'个'|'万人'|'万个'|'次'|'万次'|'ts'|'%'|'num'|'B'| 'KB'| 'MB'| 'GB'| 'TB'|'Bps'| 'KBps'| 'MBps'| 'GBps'| 'TBps'} unit
 */
/**
 * 单位进制换算公共方法
 * @class UnitFormat
 * @classdesc 单位进制工具类
 * @param {number} value 数值
 * @param {unit} unit 单位
 * @param {object} config 配置
 */
const UnitFormat = (value, unit, config) => {
  return new _UnitFormat(value, unit, config);
};
/**
 * UnitFormat默认配置项
 * decimal：2 保留小数两位
 * isMinUintFloor：true 是否最小单位向下取整
 * @constant formatConfig
 */
const formatConfig = {
  decimal: 2,
  isMinUintFloor: false,
  customCarry: NaN,
};

// 错误信息
const errorMessage = {
  default: { key: 'defalut', message: '参数错误' },
  unitError: { key: 'unitError', message: '单位不符合要求，请检查' },
  valueIsNaN: { key: 'valueIsNaN', message: '参数值value非数字，请检查' },
  valueIsNegative: { key: 'valueIsNegative', message: '参数值value为负数，请检查' },
  timestampError: {
    key: 'timestampError',
    message: '参数不符合时间戳格式，请将头函数第一个参数设置为number类型，第二个参数设置为ts',
  },
  convertError: { key: 'convertError', message: '转换类型不匹配，无法转换' },
};
// 判断是否是Byte系统单位
// 此系统单位保持大小写区分
const isBType = (u) => {
  if (Units.B_units.keys.indexOf(u) != -1 || Units.Bps_units.keys.indexOf(u) != -1) {
    return true;
  }
  return false;
};

/**
 * 内置处理类
 * @private
 */
class _UnitFormat {
  constructor(value, unit, config = formatConfig) {
    try {
      // 初始化返回值
      this.result = {
        old: {
          value,
          unit,
        },
        value,
        unit,
      };
      this.config = this.checkConfig(config);
      this.value = Number.parseFloat(value);
      // 单位值不存在的情况设置错误信息
      if (unit) {
        this.unit = isBType(unit) ? unit : unit.toLowerCase();
      } else {
        this.setError(errorMessage.unitError.key, unit, false);
        return;
      }
      this.error = false; // 初始化错误标记位
      this.errorKey = errorMessage.default.key; // 初始化错误信息key

      this.result.value = this.value;
      this.result.unit = unit;

      if (isNaN(this.value)) {
        this.setError(errorMessage.valueIsNaN.key, this.value, false);
      }
      if (!this.checkUnitType(this.unit)) {
        this.setError(errorMessage.unitError.key, this.unit, false);
      }
    } catch (e) {
      this.setError(e);
    }
  }

  /**
   * 格式化日期字符串
   * @memberof UnitFormat#
   * @function formatTimestamp
   * @param {string} formatStr 格式化日期字符串 例：YYYY-MM-DD HH:mm:ss
   * @returns {string}
   */
  formatTimestamp = (formatStr) => {
    if (this.unit != Units.ts.key) {
      return this.setError(errorMessage.timestampError.key, this.unit);
    }
    if (this.error) {
      return this.setError(null, null, false);
    }
    this.result.unit = Units.ts.key;
    this.result.value = moment(this.value).format(formatStr);
    return this.result;
  };

  /**
   * 相同单位互相转换
   * @memberof UnitFormat#
   * @function unitToUnit
   * @param {unit} newUnit 转换目的单位
   * @returns {object}
   */
  unitToUnit = (newUnit) => {
    const _newUnit = isBType(newUnit) ? newUnit : newUnit.toLowerCase(); // 单位转换成小写
    if (!this.checkUnitType(_newUnit)) {
      return this.setError(errorMessage.unitError.key, _newUnit);
    }
    if (this.error) {
      return this.setError(null, null, false);
    }
    if (Units[this.unit].type != Units[_newUnit].type) {
      // 判断源，目的单位是否同组
      this.setError(errorMessage.convertError.key, `${this.unit}->${_newUnit}`);
    } else {
      // 通过源，目的单位的进制，计算结果
      if (this.result.value < 0) {
        return this.setError(errorMessage.valueIsNegative.key, this.result.value);
      }
      const new_group = Units[Units[_newUnit].type]; // 当前单位组
      const new_carry = isNaN(this.config.customCarry) ? new_group.carry : this.config.customCarry;
      this.result.value = this.result.value * Math.pow(new_carry, Units[this.unit].carryIndex - Units[_newUnit].carryIndex);
      this.result.unit = Units[_newUnit].name; // 规范返回单位
      // 若目的单位是单位组中基础单位，则保留整数（%分号转数字除外），否则保留两位小数
      if (_newUnit === new_group.keys[0] && Units[_newUnit].type != 'bps_units') {
        if (Units[_newUnit].type != 'percent_units') {
          if (this.config.isMinUintFloor) {
            this.result.value = Math.floor(this.result.value);
          } else {
            this.result.value = toDecimal(this.result.value, this.config.decimal);
          }
        }
      } else {
        this.result.value = toDecimal(this.result.value, this.config.decimal);
      }
    }
    return this.result;
  };

  /**
   * @memberof UnitFormat#
   * @function fitUnit
   * 单位进制自动转换，流量流速千进制，人，个万进制,%保留两位小数，时间戳-2018-06-11 12:28
   * @returns {object}
   */
  fitUnit = () => {
    if (this.error) {
      return this.setError(null, null, false);
    }
    try {
      // 时间戳转换（非进制转换）
      if (this.unit === Units.ts.key) {
        this.result.unit = Units.ts.key;
        this.result.value = moment(this.value).format(Units.dateFormat_default);
        return this.result;
      }
      // 百分号 数字只做保留两位小数处理处理（非进制转换）
      if (this.unit === Units['%'].name || this.unit === Units.num.name) {
        this.result.unit = this.unit;
        this.result.value = toDecimal(this.value, this.config.decimal);
        return this.result;
      }

      // 流量，流速，秒毫秒，万进制单位转换（进制转换）
      let num = this.value;
      const old_group = Units[Units[this.unit].type]; // 当前单位类型组
      const old_carry = isNaN(this.config.customCarry) ? old_group.carry : this.config.customCarry;
      num *= Math.pow(old_carry, Units[this.unit].carryIndex); // 将单位值转换成该类型单位最小单位
      if (num < 0) {
        return this.setError(errorMessage.valueIsNegative.key, num);
      }
      let carryIndex = num < 1 ? 0 : Math.floor(Math.log(num) / Math.log(old_carry)); // 计算出数值对于该单位进制指数
      // 对于超出单位组最大单位的指数，记为最大单位指数，不向后进位
      if (carryIndex >= old_group.keys.length) {
        carryIndex = old_group.keys.length - 1;
      }
      const target_unit = old_group.keys[carryIndex]; // 目标单位
      num /= Math.pow(old_carry, Units[target_unit].carryIndex); // 单位值向目标单位转换
      // 最小单位保留整数，其他保留两位小数
      if (carryIndex === 0 && Units[this.unit].type != 'bps_units' && this.config.isMinUintFloor) {
        num = Math.floor(num);
      } else {
        num = toDecimal(num, this.config.decimal);
      }
      this.result.unit = Units[target_unit].name; // 单位规范
      this.result.value = num;
      return this.result;
    } catch (e) {
      return this.setError(e);
    }
  };

  /**
   * 适配配置项
   * @memberof UnitFormat#
   * @function checkConfig
   * @param config
   * @returns {{customCarry: number, isMinUintFloor: boolean, decimal: number}}
   */
  checkConfig = (config) => {
    let _config = formatConfig;
    // 默认配置和参数配置组装
    if (config) {
      _config = { ..._config, ...config };
    }
    // 保留小数位校验
    if (isNaN(_config.decimal)) {
      _config.decimal = formatConfig.decimal;
    } else if (_config.decimal < 0) {
      _config.decimal = formatConfig.decimal;
    }
    return _config;
  };

  /**
   * 检查单位是否符合要求
   * @param unit
   * @memberof UnitFormat#
   * @function checkUnitType
   * @returns {boolean}
   */
  checkUnitType = (unit) => {
    let hasUnit = false;
    for (const key in Units) {
      if (Units.hasOwnProperty(key)) {
        const element = Units[key];
        if (element.hasOwnProperty('key')) {
          const _unit = element.key;
          if (_unit === unit) {
            hasUnit = true;
          }
        }
      }
    }
    return hasUnit;
  };

  /**
   * 输入错误时设置错误的返回值
   * @param unit
   * @memberof UnitFormat#
   * @function setError
   * @param errorKey
   * @param info
   * @param isLog
   * @returns {{unit: *, old: {unit: *, value: *}, value: *}}
   */
  setError = (errorKey, info = '', isLog = false) => {
    let message = '';
    if (errorKey) {
      if (errorKey.message) {
        message = `tryCatchLog:${errorKey.message}`;
      } else {
        message = errorKey;
        if (errorMessage[errorKey]) {
          this.errorKey = errorKey;
          message = errorMessage[this.errorKey].message;
        }
        message += `(${info})`;
      }
    }
    if (isLog) {
      console.log(message);
    }
    // 设置返回值为默认空值
    this.result.value = Units.empty;
    this.result.unit = Units.empty;
    this.error = true; // 设置错误标记位为true
    return this.result;
  };
}

/**
 * 求数字整数位数
 * @function getNumberLength
 * @param n
 * @returns {number}
 */
const getNumberLength = (n) => {
  return Math.round(n).toString().length;
};

/**
 ***********************************************************
 *
 *   日期、时间相关公共方法
 *
 ********************************************************** */
/**
 * 将时间转换成时分秒显示格式文字
 * 不足1分钟显示秒，超过1分钟只显示小时分钟，向下舍去秒数据
 * @function HMSformat
 * @returns {string}
 * @param {number} value
 * @param {'ms'|'s'|'m'|'h'|'d'|'w'|'M'|'y'} unit (ms/s/m/h/d/w/M/y)
 * @returns {string}
 * @example   将时间转换成时分秒。显示格式文字不足1分钟显示秒，超过1分钟只显示小时分钟，向下舍去秒数据。
 * let result = DateFormat(63,'s').HMSformat();
 * // result 1分钟
 * let result = DateFormat(59,'s').HMSformat();
 * // result 59秒
 * let result = DateFormat(61,'m').HMSformat();
 * // result 1小时1分钟
 * --------------------------------------------------------------------------
 */
const HMSformat = (value, unit) => {
  if (isNaN(value)) return '-';
  try {
    const h = moment.duration(value, unit).as('h');
    const h_int = Math.floor(h);
    const h_decima = h - h_int;

    const m = moment.duration(h_decima, 'h').as('m');
    const m_int = Math.floor(m);
    const m_decima = m - m_int;

    const s = moment.duration(m_decima, 'm').as('s');
    const s_int = Math.floor(s);

    let formatString = '';
    if (h_int != 0) {
      formatString += `${h_int  }小时`;
    }
    if (m_int != 0) {
      formatString += `${m_int  }分钟`;
    }
    if (formatString === '') {
      formatString += `${s_int  }秒`;
    }
    return formatString;
  } catch (error) {
    return '-';
  }
};

/**
 * ---
 *  获取对象或数组的属性对应值
 *  * 主要用于深度获取未知对象的值.
 * @param {(array | object)} obj  对象或者数组
 * @param {string} key  对象的key值
 * @param {string} [split="."]  分隔符
 * @return {*} 返回key对应的任何数据格式的值
 * @function get
 * @static
 * @example 01 常见获取值
 * --------------------------------------------------------------------------
 * import { get } from '@riil-frontend/utils';
 * const obj = { a: { b: { c: [{'.f.m':'age'}, { d: 'name' }] } } };
 * get(obj, 'a.b.c.1.d');  // "name"
 *
 * @example 02 未知深度
 * --------------------------------------------------------------------------
 * get(obj, 'm.b.c.1.d');  // undefined
 * @example 03 自定义分割
 * --------------------------------------------------------------------------
 * get(obj, 'a@b@c@0@.f.m', '@') //age
 *
 */
const get = (obj, key, split = '.') => {
  if (obj && typeof key == 'string') {
    const keys = key.split(split);
    let value = obj;
    for (let i = 0; i < keys.length; i++) {
      const _value = value[keys[i]];
      if ('undefined' != typeof _value) {
        value = _value;
      } else {
        return undefined;
      }
    }
    return value;
  }
  return undefined;
};

const S4 = () => {
  return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
/**
 * ---
 * 生成UUID<br/>
 * @function getUUID
 * @static
 * @returns {string}
 * @example 01 生成UUID
 * --------------------------------------------------------------------------
 * import {getUUID} from '@riil-frontend/utils';
 * getUUID() result // 94ddda53-e67d-1b92-6e12-295ed71b246d
 */
const getUUID = () => {
  return `${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`;
};

/**
 * [createObj 创建对象]
 * @param  {string} key   [对象的属性名]    嵌套属性key用'.'来间隔
 * @param  {string}   value [对应属性值]
 * @return {object}       [创建新对象]
 * @access private
 * *****************************************************************
 * example :
 *  Frame.Utils.createObj("a.b.c",[1,2,3])  // result: {a:{b:{c:[1,2,3]}}}
 */

const createObj = (key, value) => {
  let result = value;
  if (key) {
    const keyType = typeof key;
    const keys = keyType === 'array' ? key : key.split('.');
    for (let i = keys.length - 1; i >= 0; i--) {
      const newObj = {};
      newObj[keys[i]] = result;
      result = newObj;
    }
  }
  return result;
};
/**
 * ---
 * 设置对象或数组属性对应的值
 * * 主要用于深度获取未知对象的值.
 * @param object 需要呗设置的对象
 * @param {string} key  设置的key属性
 * @param {*} val  key对应的value值
 * @returns {object} 设置后的对象值
 * @static
 * @todo 后续继续扩展split和array
 * @function set
 * @example 01 设置属性值
 * --------------------------------------------------------------------------
 * import { set } from '@riil-frontend/utils';
 * const obj = { a: { b: { c: [{ }, { d: 'name' }] } } };
 * set(obj, 'a.b.c.1.d','newName');
 * // result
 * { a: { b: { c: [{ }, { d: 'newName' }] } } };
 * @example 01 设置不存在属性的值
 * --------------------------------------------------------------------------
 * set(obj, 'm.b.c.d','noProp');
 * // result
 *  { a: { b: { c: [{ }, { d: 'newName' }] } },
 *    m: { b: { c: { d: 'noProp' } } }
 *  };
 */

const set = (object, key, val) => {
  const obj = object || {};
  if (typeof key === 'string') {
    const keys = key.split('.');
    let value = obj;
    for (let i = 0; i < keys.length; i++) {
      const _value = value[keys[i]];
      const _type = typeof _value;
      if (_type === 'object' || _type === 'array') {
        if (i === keys.length - 1) {
          value[keys[i]] = val;
          break;
        }
        value = value[keys[i]];
      } else {
        if (i === keys.length - 1) {
          value[keys[i]] = val;
          break;
        }
        const _keys = keys.slice(i + 1, keys.length);
        value[keys[i]] = createObj(_keys, val);
        break;
      }
    }
  }
  return obj;
};

/**
 * ---
 *  为空值判断
 *  * undefined null NaN  ''  返回为true
 *  * 其外都为false (包含 0 , false ,Infinity,-Infinity)
 * @param {*} arg 判断值是否为空
 * @returns {boolean} true : 为空 ; false：不为空
 * @function isNull
 * @author libo
 * @static
 * @example  01 空值判断
 * --------------------------------------------------------------------------
 *  isNull(null);// true
 *  isNull(undefined); // true
 *  isNull(NaN); // true
 *  isNull(''); // true
 *
 *  isNull(0);  // false
 *  isNull(false); // false
 *  isNull(Infinity); // false
 *
 */

const isNull$1 = (arg) => !arg && arg !== 0 && typeof arg !== 'boolean';

/**
 * ---
 * 是否为可用的数组
 *  * 可用数组检查规则
 *  > 1. 数据类型是数组
 *  > 1. 数组至少有一个数据
 * @param {*} arr  需要判断的数组
 * @return {boolean} 是否为可用的数组 true： 表示可用 false ：表示不可用
 * @function isAvailableArray
 * @static
 * @example 01 非数组类型
 * --------------------------------------------------------------------------
 * import {isAvailableArray} from '@riil-frontend';
 * isAvailableArray(null)      // false
 * isAvailableArray(undefined) // false
 * isAvailableArray({})        // false
 * isAvailableArray([])        // false
 * isAvailableArray(() => { }) // false
 * isAvailableArray([1])       // true
 */

const isAvailableArray = (arr) => {
  return !isNull$1(arr) && Array.isArray(arr) && arr.length > 0;
};

/**
 * ---
 * 深度拷贝对象<br/>
 *  * 在JS中对应引用在很多场景下会出现问题，因为引用对象改变值后，所有使用该对象处都发生了变化(这往往不是想要的)。
 *  * 深度拷贝后的对象，与原来的对象不存在引用同一个地址情况。
 * @param {object} obj  需要深度拷贝的对象
 * @return {object}  深度拷贝后的新对象
 * @static
 * @function deep
 * @example 01 深度拷贝
 * --------------------------------------------------------------------------
 * import { deep } from '@riil-frontend/utils';
 * let obj = {name:'name'};
 * let objA = obj;
 * objA.age = '18';
 * console.log(obj.age) // 18    // 不希望改变原来对象的值
 * console.log(objA.age) // 18
 *
 * let deepObj = deep(obj);
 * deepObj.address = 'beijing';
 * console.log(obj.address) // undefined
 * console.log(deepObj.address) // "beijing"
 *
 */

const deep = (obj) => {
  return fromJS(obj).toJS();
};

/**
 * ---
 * 时间段标识
 * * 配合getServerCurrentTime方法使用
 * 1. RANGE_TYPE.LAST_ONE_HOUR 最近一个小时
 * 2. RANGE_TYPE.LAST_HALF_HOUR 最近半小时
 * 3. RANGE_TYPE.LAST_SIX_HOUR 最近六个小时
 * 4. RANGE_TYPE.LAST_FOUR_HOUR 最近四个小时
 * 5. RANGE_TYPE.LAST_ONE_DAY 最近1天
 * 6. RANGE_TYPE.LAST_SEVEN_DAY 最近七天
 * @constant {object} 常量_RANGE_TYPE
 *
 */
const RANGE_TYPE = {
  LAST_ONE_HOUR: 'LAST_ONE_HOUR',
  LAST_HALF_HOUR: 'LAST_HALF_HOUR',
  LAST_SIX_HOUR: 'LAST_SIX_HOUR',
  LAST_FOUR_HOUR: 'LAST_FOUR_HOUR',
  LAST_ONE_DAY: 'LAST_ONE_DAY',
  LAST_SEVEN_DAY: 'LAST_SEVEN_DAY',
};

/**
 *  ---
 *  获取 URL 传递所有参数<br/>
 *
 * @returns  {array}  返回全部参数组装的对象
 * @function  getURLParam
 * @author    libo
 * @static
 * @example example 01 缺省key值
 * --------------------------------------------------------------------------
 *
 *  import { getURLParam } from '@riil-frontend/utils';
 *  getURLParam();
 *
 *
 *  eg: url = http://localhost/riil-biz-frontend/dist/#/module/function?id=123&name=appbir&ts=1571105560850
 *  // result
 *  {
 *    "id": "123",
 *    "name": "appbir",
 *    "ts": "1571105560850"
 *  }
 *
 */

const getUrlParam = () => {
  const href = location.href;
  const paramStr = href.split('?')[1];
  const paramObj = {};

  if (paramStr) {
    const params = paramStr.split('&');
    params.map(function(str) {
      const pair = str.split('=');
      paramObj[pair[0]] = pair[1];
    });
  }

  return paramObj;
};

/**
 *  ---
 *  获取 URL 传递指定参数<br/>
 *
 * @param    {string | undefined}  [key=undefined]  参数key键
 * @returns  {string | object}  key键对应的值  默认返回全部的参数值
 * @function  getURLParamByKey
 * @author    libo
 * @static
 * @example example 01 缺省key值
 * --------------------------------------------------------------------------
 *
 *  import { getURLParamByKey } from '@riil-frontend/utils';
 *  getURLParamByKey();
 *
 *
 *  eg: url = http://localhost/riil-biz-frontend/dist/#/module/function?id=123&name=appbir&ts=1571105560850
 *  // result
 *  {
 *    "id": "123",
 *    "name": "appbir",
 *    "ts": "1571105560850"
 *  }
 * @example example 02 指定key
 * --------------------------------------------------------------------------
 * getURLParamByKey('id');
 *
 * // result
 * "123"
 *
 */
const getURLParamByKey = (key) => {
  const URLParam = getUrlParam();
  return key ? URLParam[key] : URLParam;
};

/**
 * 格式化后台规则表达式
 * @param {String} operator 规则中阈值表达式,后端规则配置中子规则中的operator值
 * @param {Number} threshold 规则阈值,后端规则配置中子规则中的threshold值
 * @param {Number} value 指标的值
 * @return {object} {label：{des,ope},result:boolean}
 * @function fromatRuleOperator
 * @static
 * @example 01 获取阈值文本显示
 * --------------------------------------------------------------------------
 *  import {fromatRuleOperator} from '@riil-frontend/utils';
 *  fromatRuleOperator({'equals',threshold:0.7});// return {label:{des:'等于0.7',ope:'=0.7'},result:null}
 * @example 02 获取阈值文本显示以及比较后结果
 * --------------------------------------------------------------------------
 *  import {fromatRuleOperator} from '@riil-frontend/utils';
 *  fromatRuleOperator({'equals',threshold:0.7,value:1});// return {label:{des:'等于0.7',ope:'=0.7'},result:false}
 */
const fromatRuleOperator = (operator, threshold, value) => {
  let label = null;
  let result = null;

  if (operator === 'equals') {
    result = value === threshold;
    label = {
      des: '\u7B49\u4E8E'.concat(threshold),
      ope: '='.concat(threshold),
    };
  } else if (operator === 'gt') {
    result = value > threshold;
    label = {
      des: '\u5927\u4E8E'.concat(threshold),
      ope: '>'.concat(threshold),
    };
  } else if (operator === 'lt') {
    result = value < threshold;
    label = {
      des: '\u5C0F\u4E8E'.concat(threshold),
      ope: '<'.concat(threshold),
    };
  }

  return {
    label,
    result,
  };
};

/**
 * aes加密字符串，用于npv
 * @param str 待加密的字符串
 * @function aesEncryption
 * @access private
 */

const aesEncryption = function aesEncryption(str) {
  if (isNull$1(str)) {
    console.error('input is invalid');
    return null;
  } // 加密密钥

  const key = 'UITN25LMUQC436AH';
  const plaintkey = CryptoJS.enc.Utf8.parse(key);
  return CryptoJS.AES.encrypt(str, plaintkey, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7,
  }).toString();
};
/**
 * ---
 * 字符串加密<br/>
 * * 'ASC' 加密   当前只有一种加密  后续会增加
 * @param {string} string 需要加密的字符
 * @param {string} [type="ASC"]  加密类型
 * @returns {null} 加密后的字符串
 * @function encryption
 * @static
 * @example 01 asc 加密
 * --------------------------------------------------------------------------
 * import {encryption} from '@riil-frontend/utils';
 * let string = 'riil猎豹';
 * encryption(string);
 * // result
 * "uoLPE3xmBdjHLXMudacZAg=="
 *
 */
const encryption = (string, type = 'ASC') => {
  if (type === 'ASC') {
    return aesEncryption(string);
  }
  return string;
};

/**
 * 函数防抖：将几次操作合并为一次操作进行。原理是维护一个计时器，规定在delay时间后触发函数，
 * 但是在delay时间内再次触发的话，就会取消之前的计时器而重新设置。这样一来，只有最后一次操作能被触发。
 * @param {*} func 被防抖函数
 * @param {*} delay 等待的时间(ms)
 * @param {*} immediate true表示立即执行然后再等待，false表示进入等待，达到条件再触发
 * @returns {func} func's result
 * @function debounce
 * @static
 * @example
 * --------------------------------------------------------------------------
 * import {debounce} from '@riil-frontend/utils';
 * debounce(func, 1000，true);
 * debounce(func, 1000);
 */
const debounce = (func, delay, immediate) => {
  let timeout;

  return function() {
    const args = arguments;
    const context = this;
    //每次清除已有的计时器重新计时
    if (timeout) clearTimeout(timeout);
    //执行后再等待
    if (immediate) {
      //判断是否满足立即执行的条件，
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, delay);
      //立即执行
      if (callNow) return func.apply(context, args);
    } else {
      timeout = setTimeout(function() {
        return func.apply(context, args);
      }, delay);
    }
  };
};

export { HMSformat, RANGE_TYPE, UnitFormat, Units, arrayGroupBy, arrayToTree, debounce, deep, dynamicSort, encryption, fromatRuleOperator, get, getNumberLength, getURLParamByKey, getUUID, getUrlParam, httpValidate, httpsValidate, isAvailableArray, isNull$1 as isNull, isPassForSpecification, recursionGet, removeUrlSpace, set, successContentValidate, tcpValidate, toDecimal, validatePort };
//# sourceMappingURL=utils.js.map
