/**
 * 表单验证
 * 默认导出为验证类的实例：
 *  所以不要存在并行验证的情况
 * 如果需要并行验证
 *  请自行实例化验证类
 *
 * Validation().data($data).rules({
 *  '字段名': {
 *    title: '字段中文名称',
 *    msg: '验证错误提示语【可选】',
 *    rules: [
 *      {
 *        required: true,
 *        msg: '该条规则验证错误提示语【可选】' // 注：优先级高于msg
 *      }
 *    ],
 *    // 自定义验证【可选】
 *    validator(value) {
 *       return false // 验证通过
 *    }
 *  },
 *  field1: {
 *    title: '字段1',
 *    msg: '字段1必须符合规则',
 *    rules: [
 *      { required: true, msg: '必填' },
 *      { isIP: true, msg: '是否是正确的ip' },
 *      { isURL: true, msg: '是否是正确的URL' },
 *      { isPostcode: true, msg: '是否是正确的邮政编码' },
 *      { isCellphone: true, msg: '是否是正确的手机号' },
 *      { isfixedphone: true, msg: '是否是正确的固定电话' },
 *      { isEmail: true, msg: '是否是正确的邮箱地址' },
 *      { isAvaiableLength: {min: 0, max: 100}, msg: '字符串长度是否在某个范围' },
 *      { isString: true, msg: '是否字符串' },
 *      { isNumber: true, msg: '是否是数字' },
 *      { isObj: true, msg: '是否是对象' },
 *      { isArray: true, msg: '是否是数组' },
 *      { isDate: true, msg: '是否是日期' },
 *      { isBoolean: true, msg: '是否是boolean' },
 *      { isFunction: true, msg: '是否是函数' },
 *      { isNull: true, msg: '是否是null' },
 *      { isUndefined: true, msg: '是否是undefined' },
 *      { isCardID: true, msg: '是否是正确的身份证号' },
 *      { isLetterOnly: true, msg: '只能是字母' },
 *      { isNumberOnly: true, msg: '只能是数字' },
 *      { isLetterOrNumberOnly: true, msg: '只能是数字字母' },
 *      { min: true, msg: '不能小于' },
 *      { max: true, msg: '不能大于' },
 *      { range: true, msg: '在某个范围内' },
 *      { regex: true, msg: '自定义验证函数' },
 *    ],
 *    validator: function(value) {
 *      return false;// 验证通过
 *    }
 *  }
 *}).check();
 *
 */

export class Verification {
  constructor() {
    // 初始化验证规则集合
    this._initValidators()
    // 验证类错误提示语
    this.lang = {
      _valida_required_: '不能为空',
      _valida_isIP: '不是一个正确的IP',
      _valida_isURL: '不是一个正确的URL',
      _valida_isPostcode: '不是一个正确的邮政编码',
      _valida_isCellphone: '不是一个正确的手机号',
      _valida_isfixedphone: '不是一个正确的座机号',
      _valida_isEmail: '不是一个正确邮箱地址',
      _valida_isAvaiableLength: '',
      _valida_isString: '',
      _valida_isNumber: '',
      _valida_isObj: '',
      _valida_isArray: '',
      _valida_isDate: '',
      _valida_isBoolean: '',
      _valida_isFunction: '',
      _valida_isNull: '',
      _valida_isUndefined: '',
      _valida_isCardID: '',
      _valida_isLetterOnly: '',
      _valida_isNumberOnly: '',
      _valida_isLetterOrNumberOnly: '',
      _valida_min: '',
      _valida_max: '',
      _valida_range: '',
      _valida_regex: ''
    }
  }

  /**
   * 初始化实例数据
   */
  _initData() {
    this.errorCount = 0
    this.errorList = {}
  }

  /**
   * 设置待验证的数据
   * @param {*} d 待验证的数据
   * @returns this用于链式调用
   */
  data(d = {}) {
    this._initData()
    this.waitCheckData = d
    return this
  }

  /**
   * 验证规则
   * @param {object} r 设置验证数据中每个字段使用的规则集合
   * @returns this用于链式调用
   */
  rules(r = {}) {
    this.waitUseRules = r
    return this
  }

  /**
   * 开始验证
   * @param {number} type 验证类型 1 - 只要遇到第一个不符合的就返回结果 ；2 - 验证全部字段，返回所有不符合规范的字段
   * @returns {(boolean|string|object)} 返回false验证成功，返回字符串（错误提示语）验证失败，返回对象（错误集合）验证失败
   */
  check(type = 1) {
    for (let field in this.waitUseRules) {
      const errMsg = this.checkFieldRule(
        this.waitCheckData[field], // 字段值 value
        this.waitUseRules[field], // 验证规则集合
        field // 字段名
      )
      if (errMsg) {
        if (type === 1) {
          return `${this.waitUseRules[field].title}: ${errMsg}`
        }
      }
    }
    if (this.errorCount === 0) {
      return false
    }
    return this._getError()
  }

  /**
   * 获取错误集合
   * @returns {object} 错误对象
   */
  _getError() {
    return this.errorList
  }

  /**
   * 设置错误
   * @param {*} title 字段中文提示
   * @param {*} field 字段名
   * @param {*} errMsg 错误提示语
   */
  _setEorror(title, field, errMsg) {
    if (!this.errorList[field]) {
      this.errorList[field] = {
        title: title,
        field: field,
        msg: errMsg
      }
      this.errorCount += 1
    } else {
      this.errorList[field].msg += ',' + errMsg
    }
  }

  /**
   * 验证某个指定字段的规则
   * @param {*} value 字段值
   * @param {*} waitUseRules 字段的验证规则集合
   * @param {string} field 字段名称
   * @returns {(boolean|string)} false：验证通过
   */
  checkFieldRule(value, waitUseRules, field) {
    let _rules = [] // 验证规则数组
    let errMsg = false

    if (waitUseRules.rules) {
      if (
        Object.prototype.toString.call(waitUseRules.rules).slice(8, -1) ===
        'Array'
      ) {
        _rules = waitUseRules.rules
      } else {
        _rules = [waitUseRules.rules]
      }

      for (let i = 0, leng = _rules.length; i < leng; i++) {
        if (_rules[i]) {
          errMsg = this._doCheck(value, _rules[i], field)
          // _rules[i] = { isIP: true, msg: '是否是正确的ip111' }
          if (errMsg) {
            errMsg = _rules[i].msg || (waitUseRules.msg || errMsg)
            this._setEorror(waitUseRules.title, field, errMsg)
            break
          }
        }
      }
    }
    if (!errMsg) {
      // 自定义验证规则
      if (waitUseRules.validator) {
        errMsg = waitUseRules.validator.call(this, value)
      }
      if (errMsg) {
        this._setEorror(waitUseRules.title, field, errMsg)
      }
    }
    return errMsg
  }

  /**
   * 执行验证
   * @param {*} value 字段值
   * @param {*} rule 验证规则名称
   * @param {string} field 字段名
   * @returns {(boolean|string)}  false：验证通过
   */
  _doCheck(value, rule, field) {
    let errMsg = false
    for (let method in rule) {
      // { isIP: true, msg: '是否是正确的ip111' }
      if (method !== 'msg') {
        // 判断方法是否存在
        if (this._methodIsEmpty(method)) {
          let args = rule[method] // 验证方法所需要的参数
          const _errMsg = this.validators[method](value, args)
          if (_errMsg) {
            errMsg = _errMsg
            break
          }
        }
      }
    }
    return errMsg
  }

  /**
   * 判断验证规则是否存在
   * @param {*} value 待验证方法名
   * @returns {boolean} true：存在；false：不存在
   */
  _methodIsEmpty(value) {
    if (this.validators[value]) {
      return true
    }
    return false
  }

  /**
   * 判断是否为空
   * @param {*} value
   */
  _isEmpty(value) {
    if (
      value === '' ||
      value === undefined ||
      value === null ||
      value === 'null' ||
      value === 'undefined'
    ) {
      return true
    }
    return false
  }

  /**
   * 初始化自带验证规则
   */
  _initValidators() {
    let that = this
    that.validators = {
      /**
       * 验证必填
       * @param {*} value
       * @param {*} args
       * @returns false：验证通过
       */
      required(value, args) {
        let res = false
        if (Object.prototype.toString.call(value).slice(8, -1) === 'String') {
          res = value.replace(/\s+/g, '').length > 0
        } else if (
          Object.prototype.toString.call(value).slice(8, -1) === 'Array'
        ) {
          // 只验证数组 类型
          res = value.length > 0
        } else if (!that._isEmpty(value)) {
          res = true
        }
        return !res ? that.lang['_form_valida_required_'] : false
      },
      /**
       *
       * @descrition:判断是否是合理的IP地址
       * @param:value->待验证的IP地址
       * @returns {(boolean|string)}  false：验证通过
       *
       */
      isIP(value, args) {
        let res = false
        var pattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/

        res = pattern.test(value)

        return !res ? that.lang['_valida_isIP'] : false
      },
      /**
       * @descrition:判断输入的参数是否是个合格的URL,由于url的灵活性和多样性，一下代码并不能测试所有的url都是合法的（PS：该正则无法通配所有的URL，请慎用）
       * @param:value->待判断的url参数
       * @returns {(boolean|string)}  false：验证通过
       **/
      isURL(value, args) {
        let res = false

        var strRegex =
          '^((https|http|ftp|rtsp|mms)?://)' +
          "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" + // ftp的user@
          '(([0-9]{1,3}.){3}[0-9]{1,3}' + // IP形式的URL- 199.194.52.184
          '|' + // 允许IP和DOMAIN（域名）
          "([0-9a-z_!~*'()-]+.)*" + // 域名- www.
          '([0-9a-z][0-9a-z-]{0,61})?[0-9a-z].' + // 二级域名
          '[a-z]{2,6})' + // first level domain- .com or .museum
          '(:[0-9]{1,4})?' + // 端口- :80
          '((/?)|' +
          "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$"
        var pattern = new RegExp(strRegex)

        res = pattern.test(value)

        return !res ? that.lang['_valida_isIP'] : false
      },
      /**
       *
       * @descrition:判断输入的参数是否是国内合法的邮编地址(ps:国内不包含国外的邮编)
       * @link: http://www.youbianku.com/%E9%A6%96%E9%A1%B5
       * @param: value 为待验证的邮编号码
       * @returns {(boolean|string)}  false：验证通过
       *
       */
      isPostcode(value, args) {
        let res = false
        var pattern = /^[0-8]\d{5}$/

        res = pattern.test(value)

        return !res ? that.lang['_valida_isPostcode'] : false
      },
      /**
       *
       * @descrition:判断输入的参数是否是个合格的手机号码，不能判断号码的有效性，有效性可以通过运营商确定。
       * @param:value ->待判断的手机号码
       * @returns {(boolean|string)}  false：验证通过
       *
       */
      isCellphone(value, args) {
        let res = false
        /**
         *@descrition:手机号码段规则
         * 13段：130、131、132、133、134、135、136、137、138、139
         * 14段：140、141、142、143、144、145、146、147、148、149
         * 15段：150、151、152、153、154、155、156、157、158、159
         * 16段：160、161、162、163、164、165、166、167、168、169
         * 17段：170、171、172、173、174、175、176、177、178、179
         * 18段：180、181、182、183、184、185、186、187、188、189
         * 19段：190、191、192、193、194、195、196、197、198、199
         */
        var pattern = /^[1][3,4,5,6,7,8,9][0-9]\d{8}$/

        res = pattern.test(value)

        return !res ? that.lang['_valida_isCellphone'] : false
      },
      /**
       * @descrition:判断输入的参数是否是个合格的固定电话号码。
       * @param:value->待验证的固定电话号码。
       * @returns {(boolean|string)}  false：验证通过
       *
       **/
      isfixedphone(value, args) {
        let res = false
        /**
         *
         * @desctition:规则->区号3-4位，号码7-8位,可以有分机号，分机号为3-4为，格式如下："0775-85333333-123"
         *
         */
        var pattern = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/

        res = pattern.test(value)

        return !res ? that.lang['_valida_isfixedphone'] : false
      },
      /**
       *
       * @descrition:判断输入的参数是否是个合格标准的邮箱,并不能判断是否有效，有效只能通过邮箱提供商确定。
       * @param:value ->待验证的参数。
       * @returns {(boolean|string)}  false：验证通过
       *
       */
      isEmail(value, args) {
        let res = false
        /**
         * @descrition:邮箱规则
         * 1.邮箱以a-z、A-Z、0-9开头，最小长度为1.
         * 2.如果左侧部分包含-、_、.则这些特殊符号的前面必须包一位数字或字母。
         * 3.@符号是必填项
         * 4.右则部分可分为两部分，第一部分为邮件提供商域名地址，第二部分为域名后缀，现已知的最短为2位。最长的为6为。
         * 5.邮件提供商域可以包含特殊字符-、_、.
         */
        var pattern = /^([a-zA-Z0-9]+[-_.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[-_.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,6}$/

        res = pattern.test(value)

        return !res ? that.lang['_valida_isEmail'] : false
      },
      /**
       * 判断传入的参数的长度是否在给定的有效范围内
       * @param->value   : 传入的参数，参数可以为任何类型。
       * @param {*} args 验证需要的参数
       * @returns {(boolean|string)}  false：验证通过
       *
       */
      isAvaiableLength(value, args = { min: 0, max: 0 }) {
        let res = false

        if (that._isEmpty(value)) {
          res = false
        } else {
          if (value.length >= args.min && value.length <= args.max) {
            res = true
          }
        }
        return !res ? that.lang['_valida_isAvaiableLength'] : false
      },
      /**
       * 判断是否字符串
       * @param->value   : 传入的参数，参数可以为任何类型。
       * @returns {(boolean|string)}  false：验证通过
       */
      isString(value, args) {
        let res = false
        // 是否字符串
        if (Object.prototype.toString.call(value).slice(8, -1) === 'String') {
          res = true
        }

        return !res ? that.lang['_valida_isString'] : false
      },
      /**
       *
       * @descrition : 该函数的功能是判断转入的参数是否为数字类型。
       * @param->value   : 传入的参数，参数可以为任何类型。
       * @returns {(boolean|string)}  false：验证通过
       *
       */
      isNumber(value, args) {
        let res = false
        // 是否数字
        if (Object.prototype.toString.call(value).slice(8, -1) === 'Number') {
          res = true
        }

        return !res ? that.lang['_valida_isNumber'] : false
      },
      isObj(value, args) {
        // 是否对象
        let res = false
        if (Object.prototype.toString.call(value).slice(8, -1) === 'Object') {
          res = true
        }

        return !res ? that.lang['_valida_isObj'] : false
      },
      isArray(value, args) {
        // 是否数组
        let res = false
        if (Object.prototype.toString.call(value).slice(8, -1) === 'Array') {
          res = true
        }

        return !res ? that.lang['_valida_isArray'] : false
      },
      isDate(value, args) {
        // 是否时间
        let res = false
        if (Object.prototype.toString.call(value).slice(8, -1) === 'Date') {
          res = true
        }

        return !res ? that.lang['_valida_isDate'] : false
      },
      isBoolean(value, args) {
        // 是否boolean
        let res = false
        if (Object.prototype.toString.call(value).slice(8, -1) === 'Boolean') {
          res = true
        }

        return !res ? that.lang['_valida_isBoolean'] : false
      },
      isFunction(value, args) {
        // 是否函数
        let res = false
        if (Object.prototype.toString.call(value).slice(8, -1) === 'Function') {
          res = true
        }

        return !res ? that.lang['_valida_isFunction'] : false
      },
      isNull(value, args) {
        // 是否为null
        let res = false
        if (Object.prototype.toString.call(value).slice(8, -1) === 'Null') {
          res = true
        }

        return !res ? that.lang['_valida_isNull'] : false
      },
      isUndefined(value, args) {
        // 是否undefined
        let res = false
        if (
          Object.prototype.toString.call(value).slice(8, -1) === 'Undefined'
        ) {
          res = true
        }

        return !res ? that.lang['_valida_isUndefined'] : false
      },
      isCardID(value, args) {
        let res = true

        if (!/(^\d{15}$)|(^\d{17}(\d|X|x)$)/.test(value)) {
          res = false
        }
        // 身份证城市
        var aCity = {
          11: '北京',
          12: '天津',
          13: '河北',
          14: '山西',
          15: '内蒙古',
          21: '辽宁',
          22: '吉林',
          23: '黑龙江',
          31: '上海',
          32: '江苏',
          33: '浙江',
          34: '安徽',
          35: '福建',
          36: '江西',
          37: '山东',
          41: '河南',
          42: '湖北',
          43: '湖南',
          44: '广东',
          45: '广西',
          46: '海南',
          50: '重庆',
          51: '四川',
          52: '贵州',
          53: '云南',
          54: '西藏',
          61: '陕西',
          62: '甘肃',
          63: '青海',
          64: '宁夏',
          65: '新疆',
          71: '台湾',
          81: '香港',
          82: '澳门',
          91: '国外'
        }
        if (!aCity[parseInt(value.substr(0, 2))]) {
          res = false
        }

        // 出生日期验证
        var sBirthday = (
          value.substr(6, 4) +
          '-' +
          Number(value.substr(10, 2)) +
          '-' +
          Number(value.substr(12, 2))
        ).replace(/-/g, '/')
        var d = new Date(sBirthday)
        if (
          sBirthday !==
          d.getFullYear() + '/' + (d.getMonth() + 1) + '/' + d.getDate()
        ) {
          res = false
        }

        // 身份证号码校验
        var sum = 0
        var weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
        var codes = '10X98765432'
        for (var i = 0; i < value.length - 1; i++) {
          sum += value[i] * weights[i]
        }
        var last = codes[sum % 11] // 计算出来的最后一位身份证号码
        if (value[value.length - 1] !== last) {
          res = false
        }

        return !res ? that.lang['_valida_isCardID'] : false
      },
      // 验证只能是字母
      isLetterOnly(value) {
        let res = false
        var pattern = /^[a-zA-Z]*$/

        res = pattern.test(value)

        if (value === '') {
          res = true
        }

        return !res ? that.lang['_valida_isLetterOnly'] : false
      },
      // 验证只能为数字
      isNumberOnly(value) {
        let res = false
        var pattern = /^[0-9]+$/

        res = pattern.test(value)

        if (value === '') {
          res = true
        }

        return !res ? that.lang['_valida_isNumberOnly'] : false
      },
      // 验证只能是字母和数字
      isLetterOrNumberOnly(value) {
        let res = false
        var pattern = /^[0-9a-zA-Z]*$/

        res = pattern.test(value)

        if (value === '') {
          res = true
        }

        return !res ? that.lang['_valida_isLetterOrNumberOnly'] : false
      },
      // 最小值
      min(value, args) {
        var reg = /^[0-9]+$/
        if (value !== '' && !reg.test(value)) {
          return that.lang['_valida_min']
        }
        if (args.min !== '' && !reg.test(args.min)) {
          return that.lang['_valida_min']
        }
        if (parseFloat(value) < parseFloat(args.min)) {
          return that.lang['_valida_min']
        }
        return false
      },
      // 最大值
      max(value, args) {
        var reg = /^[0-9]+$/
        if (value !== '' && !reg.test(value)) {
          return that.lang['_valida_max']
        }
        if (args.max !== '' && !reg.test(args.max)) {
          return that.lang['_valida_max']
        }
        if (parseFloat(value) > parseFloat(args.max)) {
          return that.lang['_valida_max']
        }
        return false
      },
      // 某个范围
      range(value, args = { min: 0, max: 0 }) {
        var reg = /^[0-9]+$/
        if (value !== '' && !reg.test(value)) {
          return that.lang['_valida_range']
        }
        if (args.max !== '' && !reg.test(args.max)) {
          return that.lang['_valida_range']
        }
        if (args.min !== '' && !reg.test(args.min)) {
          return that.lang['_valida_range']
        }
        if (
          parseFloat(args.min) > parseFloat(value) ||
          parseFloat(value) > parseFloat(args.max)
        ) {
          return that.lang['_valida_range']
        }
        return false
      },
      regex(value, reg) {
        let res = false
        if (reg && !reg.test(value)) {
          res = reg.test(value)
        }
        return !res ? that.lang['_valida_regex'] : false
      }
    }
  }
}
