/* Modified from https://github.com/taylorhakes/fecha
 * v4.1.0
 * hugh zhuang  把 YYYY-MM-DD 改成了 yyyy-MM-dd  与java后台 统一格式
 * YYYY修改为yyyy
 *
 * 备注：未支持多个国际化，该代码参考element-ui的日期帮助类
 *      增加日期格式处理
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Taylor Hakes
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 *     The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|Z|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g
var twoDigitsOptional = '[1-9]\\d?'
var twoDigits = '\\d\\d'
var threeDigits = '\\d{3}'
var fourDigits = '\\d{4}'
var word = '[^\\s]+'
var literal = /\[([^]*?)\]/gm
function shorten(arr, sLen) {
  var newArr = []
  for (var i = 0, len = arr.length; i < len; i++) {
    newArr.push(arr[i].substr(0, sLen))
  }
  return newArr
}
var monthUpdate = function(arrName) {
  return function(v, i18n) {
    var lowerCaseArr = i18n[arrName].map(function(v) { return v.toLowerCase() })
    var index = lowerCaseArr.indexOf(v.toLowerCase())
    if (index > -1) {
      return index
    }
    return null
  }
}
function assign(origObj) {
  var args = []
  for (var _i = 1; _i < arguments.length; _i++) {
    args[_i - 1] = arguments[_i]
  }
  for (var _a = 0, args_1 = args; _a < args_1.length; _a++) {
    var obj = args_1[_a]
    for (var key in obj) {
      // @ts-ignore ex
      origObj[key] = obj[key]
    }
  }
  return origObj
}
var dayNames = [
  'Sunday',
  'Monday',
  'Tuesday',
  'Wednesday',
  'Thursday',
  'Friday',
  'Saturday'
]
var monthNames = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December'
]
var monthNamesShort = shorten(monthNames, 3)
var dayNamesShort = shorten(dayNames, 3)
var defaultI18n = {
  dayNamesShort: dayNamesShort,
  dayNames: dayNames,
  monthNamesShort: monthNamesShort,
  monthNames: monthNames,
  amPm: ['am', 'pm'],
  DoFn: function(dayOfMonth) {
    return (dayOfMonth +
            ['th', 'st', 'nd', 'rd'][dayOfMonth % 10 > 3
              ? 0
              : ((dayOfMonth - (dayOfMonth % 10) !== 10 ? 1 : 0) * dayOfMonth) % 10])
  }
}
var globalI18n = assign({}, defaultI18n)
var setGlobalDateI18n = function(i18n) {
  return (globalI18n = assign(globalI18n, i18n))
}
var regexEscape = function(str) {
  return str.replace(/[|\\{()[^$+*?.-]/g, '\\$&')
}
var pad = function(val, len) {
  if (len === void 0) { len = 2 }
  val = String(val)
  while (val.length < len) {
    val = '0' + val
  }
  return val
}
var formatFlags = {
  d: function(dateObj) { return String(dateObj.getDate()) }, // zxh  D 修改为 d
  dd: function(dateObj) { return pad(dateObj.getDate()) }, // zxh  DD 修改为 dd
  do: function(dateObj, i18n) { // zxh  Do 修改为 do
    return i18n.DoFn(dateObj.getDate())
  },
  D: function(dateObj) { return String(dateObj.getDay()) }, // zxh  d 修改为D
  DD: function(dateObj) { return pad(dateObj.getDay()) }, // zxh  dd 修改为 DD
  DDD: function(dateObj, i18n) { // zxh  ddd 修改为 DDD
    return i18n.dayNamesShort[dateObj.getDay()]
  },
  DDDD: function(dateObj, i18n) { // zxh  dddd 修改为 DDDD
    return i18n.dayNames[dateObj.getDay()]
  },
  M: function(dateObj) { return String(dateObj.getMonth() + 1) },
  MM: function(dateObj) { return pad(dateObj.getMonth() + 1) },
  MMM: function(dateObj, i18n) {
    return i18n.monthNamesShort[dateObj.getMonth()]
  },
  MMMM: function(dateObj, i18n) {
    return i18n.monthNames[dateObj.getMonth()]
  },
  yy: function(dateObj) { // zxh 修改 YY  修改为 yy
    return pad(String(dateObj.getFullYear()), 4).substr(2)
  },
  yyyy: function(dateObj) { return pad(dateObj.getFullYear(), 4) }, // zxh 修改 YYYY  修改为 yyyy
  h: function(dateObj) { return String(dateObj.getHours() % 12 || 12) },
  hh: function(dateObj) { return pad(dateObj.getHours() % 12 || 12) },
  H: function(dateObj) { return String(dateObj.getHours()) },
  HH: function(dateObj) { return pad(dateObj.getHours()) },
  m: function(dateObj) { return String(dateObj.getMinutes()) },
  mm: function(dateObj) { return pad(dateObj.getMinutes()) },
  s: function(dateObj) { return String(dateObj.getSeconds()) },
  ss: function(dateObj) { return pad(dateObj.getSeconds()) },
  S: function(dateObj) {
    return String(Math.round(dateObj.getMilliseconds() / 100))
  },
  SS: function(dateObj) {
    return pad(Math.round(dateObj.getMilliseconds() / 10), 2)
  },
  SSS: function(dateObj) { return pad(dateObj.getMilliseconds(), 3) },
  a: function(dateObj, i18n) {
    return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1]
  },
  A: function(dateObj, i18n) {
    return dateObj.getHours() < 12
      ? i18n.amPm[0].toUpperCase()
      : i18n.amPm[1].toUpperCase()
  },
  ZZ: function(dateObj) {
    var offset = dateObj.getTimezoneOffset()
    return ((offset > 0 ? '-' : '+') +
            pad(Math.floor(Math.abs(offset) / 60) * 100 + (Math.abs(offset) % 60), 4))
  },
  Z: function(dateObj) {
    var offset = dateObj.getTimezoneOffset()
    return ((offset > 0 ? '-' : '+') +
            pad(Math.floor(Math.abs(offset) / 60), 2) +
            ':' +
            pad(Math.abs(offset) % 60, 2))
  }
}
var monthParse = function(v) { return +v - 1 }
var emptyDigits = [null, twoDigitsOptional]
var emptyWord = [null, word]
var amPm = [
  'isPm',
  word,
  function(v, i18n) {
    var val = v.toLowerCase()
    if (val === i18n.amPm[0]) {
      return 0
    } else if (val === i18n.amPm[1]) {
      return 1
    }
    return null
  }
]
var timezoneOffset = [
  'timezoneOffset',
  '[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z?',
  function(v) {
    var parts = (v + '').match(/([+-]|\d\d)/gi)
    if (parts) {
      var minutes = +parts[1] * 60 + parseInt(parts[2], 10)
      return parts[0] === '+' ? minutes : -minutes
    }
    return 0
  }
]
var parseFlags = {
  d: ['day', twoDigitsOptional], // zxh D 修改为 d
  dd: ['day', twoDigits], // zxh DD 修改为 dd
  do: ['day', twoDigitsOptional + word, function(v) { return parseInt(v, 10) }], // zxh D0 修改为 do
  M: ['month', twoDigitsOptional, monthParse],
  MM: ['month', twoDigits, monthParse],
  YY: [
    'year',
    twoDigits,
    function(v) {
      var now = new Date()
      var cent = +('' + now.getFullYear()).substr(0, 2)
      return +('' + (+v > 68 ? cent - 1 : cent) + v)
    }
  ],
  h: ['hour', twoDigitsOptional, undefined, 'isPm'],
  hh: ['hour', twoDigits, undefined, 'isPm'],
  H: ['hour', twoDigitsOptional],
  HH: ['hour', twoDigits],
  m: ['minute', twoDigitsOptional],
  mm: ['minute', twoDigits],
  s: ['second', twoDigitsOptional],
  ss: ['second', twoDigits],
  yyyy: ['year', fourDigits], // zxh YYYY修改为yyyy
  S: ['millisecond', '\\d', function(v) { return +v * 100 }],
  SS: ['millisecond', twoDigits, function(v) { return +v * 10 }],
  SSS: ['millisecond', threeDigits],
  D: emptyDigits, // zxh d 修改为 D
  DD: emptyDigits, // zxh dd 修改为 DD
  DDD: emptyWord,
  DDDD: emptyWord,
  MMM: ['month', word, monthUpdate('monthNamesShort')],
  MMMM: ['month', word, monthUpdate('monthNames')],
  a: amPm,
  A: amPm,
  ZZ: timezoneOffset,
  Z: timezoneOffset
}
// Some common format strings
var globalMasks = {
  default: 'yyyy-MM-dd HH:mm:ss',
  shortDate: 'M/d/yy',
  mediumDate: 'MMM D, yyyy',
  longDate: 'MMMM d, yyyy',
  fullDate: 'DDDD, MMMM d, yyyy',
  isoDate: 'yyyy-MM-dd',
  isoDateTime: 'yyyy-MM-ddTHH:mm:ssZ',
  shortTime: 'HH:mm',
  mediumTime: 'HH:mm:ss',
  longTime: 'HH:mm:ss.SSS'
}
var setGlobalDateMasks = function(masks) { return assign(globalMasks, masks) }
/** *
 * Format a date
 * @method format
 * @param {Date|number} dateObj
 * @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate'
 * @returns {string} Formatted date string
 */
var format = function(dateObj, mask, i18n) {
  if (dateObj === null || dateObj === undefined) return ''
  if (mask === void 0) { mask = globalMasks['default'] }
  if (i18n === void 0) { i18n = {} }
  if (typeof dateObj === 'number') {
    dateObj = new Date(dateObj)
  }
  if (Object.prototype.toString.call(dateObj) !== '[object Date]' ||
        isNaN(dateObj.getTime())) {
    throw new Error('Invalid Date pass to format')
  }
  mask = globalMasks[mask] || mask
  var literals = []
  // Make literals inactive by replacing them with @@@
  mask = mask.replace(literal, function($0, $1) {
    literals.push($1)
    return '@@@'
  })
  var combinedI18nSettings = assign(assign({}, globalI18n), i18n)
  // Apply formatting rules
  mask = mask.replace(token, function($0) {
    return formatFlags[$0](dateObj, combinedI18nSettings)
  })
  // Inline literal values back into the formatted value
  return mask.replace(/@@@/g, function() { return literals.shift() })
}
/**
 * Parse a date string into a Javascript Date object /
 * @method parse
 * @param {string} dateStr Date string
 * @param {string} format Date parse format
 * @param {i18n} I18nSettingsOptional Full or subset of I18N settings
 * @returns {Date|null} Returns Date object. Returns null what date string is invalid or doesn't match format
 */
function parse(dateStr, format, i18n) {
  if (dateStr === null || dateStr === undefined) return ''
  if (i18n === void 0) { i18n = {} }
  if (typeof format !== 'string') {
    throw new Error('Invalid format in fecha parse')
  }
  // Check to see if the format is actually a mask
  format = globalMasks[format] || format
  // Avoid regular expression denial of service, fail early for really long strings
  // https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
  if (dateStr.length > 1000) {
    return null
  }
  // Default to the beginning of the year.
  var today = new Date()
  var dateInfo = {
    year: today.getFullYear(),
    month: 0,
    day: 1,
    hour: 0,
    minute: 0,
    second: 0,
    millisecond: 0,
    isPm: null,
    timezoneOffset: null
  }
  var parseInfo = []
  var literals = []
  // Replace all the literals with @@@. Hopefully a string that won't exist in the format
  var newFormat = format.replace(literal, function($0, $1) {
    literals.push(regexEscape($1))
    return '@@@'
  })
  var specifiedFields = {}
  var requiredFields = {}
  // Change every token that we find into the correct regex
  newFormat = regexEscape(newFormat).replace(token, function($0) {
    var info = parseFlags[$0]
    var field = info[0]; var regex = info[1]; var requiredField = info[3]
    // Check if the person has specified the same field twice. This will lead to confusing results.
    if (specifiedFields[field]) {
      throw new Error('Invalid format. ' + field + ' specified twice in format')
    }
    specifiedFields[field] = true
    // Check if there are any required fields. For instance, 12 hour time requires AM/PM specified
    if (requiredField) {
      requiredFields[requiredField] = true
    }
    parseInfo.push(info)
    return '(' + regex + ')'
  })
  // Check all the required fields are present
  Object.keys(requiredFields).forEach(function(field) {
    if (!specifiedFields[field]) {
      throw new Error('Invalid format. ' + field + ' is required in specified format')
    }
  })
  // Add back all the literals after
  newFormat = newFormat.replace(/@@@/g, function() { return literals.shift() })
  // Check if the date string matches the format. If it doesn't return null
  var matches = dateStr.match(new RegExp(newFormat, 'i'))
  if (!matches) {
    return null
  }
  var combinedI18nSettings = assign(assign({}, globalI18n), i18n)
  // For each match, call the parser function for that date part
  for (var i = 1; i < matches.length; i++) {
    var _a = parseInfo[i - 1]; var field = _a[0]; var parser = _a[2]
    var value = parser
      ? parser(matches[i], combinedI18nSettings)
      : +matches[i]
    // If the parser can't make sense of the value, return null
    if (value == null) {
      return null
    }
    dateInfo[field] = value
  }
  if (dateInfo.isPm === 1 && dateInfo.hour != null && +dateInfo.hour !== 12) {
    dateInfo.hour = +dateInfo.hour + 12
  } else if (dateInfo.isPm === 0 && +dateInfo.hour === 12) {
    dateInfo.hour = 0
  }
  var dateWithoutTZ = new Date(dateInfo.year, dateInfo.month, dateInfo.day, dateInfo.hour, dateInfo.minute, dateInfo.second, dateInfo.millisecond)
  var validateFields = [
    ['month', 'getMonth'],
    ['day', 'getDate'],
    ['hour', 'getHours'],
    ['minute', 'getMinutes'],
    ['second', 'getSeconds']
  ]
  for (var j = 0, len = validateFields.length; j < len; j++) {
    // Check to make sure the date field is within the allowed range. Javascript dates allows values
    // outside the allowed range. If the values don't match the value was invalid
    if (specifiedFields[validateFields[j][0]] &&
            dateInfo[validateFields[j][0]] !== dateWithoutTZ[validateFields[j][1]]()) {
      return null
    }
  }
  if (dateInfo.timezoneOffset == null) {
    return dateWithoutTZ
  }
  return new Date(Date.UTC(dateInfo.year, dateInfo.month, dateInfo.day, dateInfo.hour, dateInfo.minute - dateInfo.timezoneOffset, dateInfo.second, dateInfo.millisecond))
}

/**
 * 格式化当前时间
 */
function formatDate(mask = globalMasks.default, i18nSettings) {
  return format(new Date(), mask, i18nSettings)
}

/**
 * 格式当前时间
 */
function parseDate(mask = globalMasks.default, i18nSettings) {
  return parse(formatDate(mask, i18nSettings), mask, i18nSettings)
}

/**
 * 相对时间字符串
 * @param {String|Number} time 时间格式字符串，或者时间
 * @returns {String} 相对时间字符串
 */
function formatRelativeTime(time, mask = {}, i18nSettings) {
  if (!i18nSettings) {
    i18nSettings = function() {}
  }
  if (!time || time === null) return ''
  let date
  if (Object.prototype.toString.call(time) === '[object Date]') {
    date = time
  } else {
    date = !isNaN(time) ? new Date(time) : parse(time, mask['orig'] || globalMasks.default)
  }
  const timeStamp = date.getTime()
  // 获取当前时间时间戳
  const currentTime = (new Date()).getTime()

  // 判断传入时间戳是否早于当前时间戳
  const IS_EARLY = timeStamp <= currentTime
  // 获取两个时间戳差值
  let diff = currentTime - timeStamp
  // 如果IS_EARLY为false则差值取反
  if (!IS_EARLY) diff = -diff
  let resStr = ''
  const dirStr = IS_EARLY ? (i18nSettings('ibps.time.before') || '前') : (i18nSettings('ibps.time.after') || '后')

  if (diff < 1000) {
    resStr = i18nSettings('ibps.time.just') || '刚刚'
  } else if (diff < 60000) {
    // 少于等于59秒
    resStr = parseInt(diff / 1000) + (i18nSettings('ibps.time.seconds') || '秒') + dirStr
  } else if (diff >= 60000 && diff < 3600000) {
    // 多于59秒，少于等于59分钟59秒
    resStr = Math.floor(diff / 60000) + (i18nSettings('ibps.time.minutes') || '分钟') + dirStr
  } else if (diff >= 3600000 && diff < 86400000) {
    // 多于59分钟59秒，少于等于23小时59分钟59秒
    resStr = Math.floor(diff / 3600000) + (i18nSettings('ibps.time.hours') || '小时') + dirStr
  } else if (diff >= 86400000 && diff < 2623860000) {
    // 多于23小时59分钟59秒，少于等于29天59分钟59秒
    resStr = Math.floor(diff / 86400000) + (i18nSettings('ibps.time.days') || '天') + dirStr
  } else if (diff >= 2623860000 && diff <= 31567860000 && IS_EARLY) {
    // 多于29天59分钟59秒，少于364天23小时59分钟59秒，且传入的时间戳早于当前
    resStr = format(timeStamp, mask['day'] || 'MM-dd HH:mm')
  } else {
    resStr = format(timeStamp, mask['year'] || globalMasks.default)
  }
  return resStr
}

const dateDealFmt = {
  $dp: {
    has: {}
  },
  dealFmt: function(dateStr, dateFmt) {
    if (dateStr === '' || dateStr === null || dateStr === undefined) { return null }
    if (dateStr.length !== dateFmt.length) { // 格式化日期不相同
      const date = new Date(dateStr.replace(/-/g, '/'))
      if (!isNaN(date.getTime())) {
        return date
      } else { // IE
        const mydate = dateStr.slice(0, dateStr.indexOf('.'))// 去掉毫秒数
        return new Date(mydate.replace(/-/g, '/'))
      }
    }

    this.$dp.dateFmt = dateFmt
    this._setHas(/w/)
    this._setHas(/WW|W/)
    this._setHas(/DD|D/)
    this._setHas(/yyyy|yyy|yy|y/)
    this._setHas(/MMMM|MMM|MM|M/)
    this._setHas(/dd|d/)
    this._setHas(/HH|H/)
    this._setHas(/mm|m/)
    this._setHas(/ss|s/)

    const date = new Date()
    for (const p in this.$dp.has) {
      const has = this.$dp.has[p]
      if (has != null) {
        const val = parseInt(dateStr.substring(has.index, has.index + has[0].length), 10)
        if (p === 'y') { date.setYear(val) }
        if (p === 'M') { date.setMonth(val - 1) }
        if (p === 'd') { date.setDate(val) }
        if (p === 'H') { date.setHours(val) }
        if (p === 'm') { date.setMinutes(val) }
        if (p === 's') { date.setSeconds(val) }
      }
    }
    return date
  },
  _setHas: function(re) {
    this.$dp.has[(re + '').slice(1, 2)] = re.exec(this.$dp.dateFmt)
  }
}

var fecha = {
  format: format,
  parse: parse,
  defaultI18n: defaultI18n,
  setGlobalDateI18n: setGlobalDateI18n,
  setGlobalDateMasks: setGlobalDateMasks,
  formatDate: formatDate,
  parseDate: parseDate,
  formatRelativeTime: formatRelativeTime
}

export default fecha
export { assign, format, parse, defaultI18n, setGlobalDateI18n, setGlobalDateMasks, formatDate, parseDate, formatRelativeTime, dateDealFmt }

