// @ts-check
import Utils from './Utils'

/**
 * DateTime Class to manipulate datetime values.
 *
 * @module DateTime
 **/

class DateTime {
  /**
   * @param {import('../types/internal').ChartStateW} w
   */
  constructor(w) {
    this.w = w

    this.months31 = [1, 3, 5, 7, 8, 10, 12]
    this.months30 = [2, 4, 6, 9, 11]

    this.daysCntOfYear = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
  }

  /**
   * @param {any} date
   */
  isValidDate(date) {
    if (typeof date === 'number') {
      return false // don't test for timestamps
    }
    return !isNaN(this.parseDate(date))
  }

  /**
   * @param {any} dateStr
   */
  getTimeStamp(dateStr) {
    if (!Date.parse(dateStr)) {
      return dateStr
    }
    const utc = this.w.config.xaxis.labels.datetimeUTC
    return !utc
      ? new Date(dateStr).getTime()
      : new Date(new Date(dateStr).toISOString().substr(0, 25)).getTime()
  }

  /**
   * @param {any} timestamp
   */
  getDate(timestamp) {
    const utc = this.w.config.xaxis.labels.datetimeUTC

    return utc
      ? new Date(new Date(timestamp).toUTCString())
      : new Date(timestamp)
  }

  /**
   * @param {string} dateStr
   */
  parseDate(dateStr) {
    const parsed = Date.parse(dateStr)
    if (!isNaN(parsed)) {
      return this.getTimeStamp(dateStr)
    }

    let output = Date.parse(dateStr.replace(/-/g, '/').replace(/[a-z]+/gi, ' '))
    output = this.getTimeStamp(output)
    return output
  }

  // This fixes the difference of x-axis labels between chrome/safari
  // Fixes #1726, #1544, #1485, #1255
  /**
   * @param {string} dateStr
   */
  parseDateWithTimezone(dateStr) {
    return Date.parse(dateStr.replace(/-/g, '/').replace(/[a-z]+/gi, ' '))
  }

  // http://stackoverflow.com/questions/14638018/current-time-formatting-with-javascript#answer-14638191
  /**
   * @param {Date} date
   * @param {string} format
   */
  formatDate(date, format) {
    const locale = this.w.globals.locale

    const utc = this.w.config.xaxis.labels.datetimeUTC

    const MMMM = ['\x00', ...locale.months]
    const MMM = ['\x01', ...locale.shortMonths]
    const dddd = ['\x02', ...locale.days]
    const ddd = ['\x03', ...locale.shortDays]

    /**
     * @param {number} i
     * @param {number} len
     */
    function ii(i, len = 2) {
      let s = i + ''
      while (s.length < len) s = '0' + s
      return s
    }

    const y = utc ? date.getUTCFullYear() : date.getFullYear()
    format = format.replace(/(^|[^\\])yyyy+/g, '$1' + y)
    format = format.replace(/(^|[^\\])yy/g, '$1' + y.toString().substr(2, 2))
    format = format.replace(/(^|[^\\])y/g, '$1' + y)

    const M = (utc ? date.getUTCMonth() : date.getMonth()) + 1
    format = format.replace(/(^|[^\\])MMMM+/g, '$1' + MMMM[0])
    format = format.replace(/(^|[^\\])MMM/g, '$1' + MMM[0])
    format = format.replace(/(^|[^\\])MM/g, '$1' + ii(M))
    format = format.replace(/(^|[^\\])M/g, '$1' + M)

    const d = utc ? date.getUTCDate() : date.getDate()
    format = format.replace(/(^|[^\\])dddd+/g, '$1' + dddd[0])
    format = format.replace(/(^|[^\\])ddd/g, '$1' + ddd[0])
    format = format.replace(/(^|[^\\])dd/g, '$1' + ii(d))
    format = format.replace(/(^|[^\\])d/g, '$1' + d)

    const H = utc ? date.getUTCHours() : date.getHours()
    format = format.replace(/(^|[^\\])HH+/g, '$1' + ii(H))
    format = format.replace(/(^|[^\\])H/g, '$1' + H)

    const h = H > 12 ? H - 12 : H === 0 ? 12 : H
    format = format.replace(/(^|[^\\])hh+/g, '$1' + ii(h))
    format = format.replace(/(^|[^\\])h/g, '$1' + h)

    const m = utc ? date.getUTCMinutes() : date.getMinutes()
    format = format.replace(/(^|[^\\])mm+/g, '$1' + ii(m))
    format = format.replace(/(^|[^\\])m/g, '$1' + m)

    const s = utc ? date.getUTCSeconds() : date.getSeconds()
    format = format.replace(/(^|[^\\])ss+/g, '$1' + ii(s))
    format = format.replace(/(^|[^\\])s/g, '$1' + s)

    let f = utc ? date.getUTCMilliseconds() : date.getMilliseconds()
    format = format.replace(/(^|[^\\])fff+/g, '$1' + ii(f, 3))
    f = Math.round(f / 10)
    format = format.replace(/(^|[^\\])ff/g, '$1' + ii(f))
    f = Math.round(f / 10)
    format = format.replace(/(^|[^\\])f/g, '$1' + f)

    const T = H < 12 ? 'AM' : 'PM'
    format = format.replace(/(^|[^\\])TT+/g, '$1' + T)
    format = format.replace(/(^|[^\\])T/g, '$1' + T.charAt(0))

    const t = T.toLowerCase()
    format = format.replace(/(^|[^\\])tt+/g, '$1' + t)
    format = format.replace(/(^|[^\\])t/g, '$1' + t.charAt(0))

    let tz = -date.getTimezoneOffset()
    let K = utc || !tz ? 'Z' : tz > 0 ? '+' : '-'

    if (!utc) {
      tz = Math.abs(tz)
      const tzHrs = Math.floor(tz / 60)
      const tzMin = tz % 60
      K += ii(tzHrs) + ':' + ii(tzMin)
    }

    format = format.replace(/(^|[^\\])K/g, '$1' + K)

    const day = (utc ? date.getUTCDay() : date.getDay()) + 1
    format = format.replace(new RegExp(dddd[0], 'g'), dddd[day])
    format = format.replace(new RegExp(ddd[0], 'g'), ddd[day])

    format = format.replace(new RegExp(MMMM[0], 'g'), MMMM[M])
    format = format.replace(new RegExp(MMM[0], 'g'), MMM[M])

    format = format.replace(/\\(.)/g, '$1')

    return format
  }

  /**
   * @param {number} minX
   * @param {number} maxX
   */
  getTimeUnitsfromTimestamp(minX, maxX) {
    const w = this.w

    if (w.config.xaxis.min !== undefined) {
      minX = w.config.xaxis.min
    }
    if (w.config.xaxis.max !== undefined) {
      maxX = w.config.xaxis.max
    }

    const tsMin = this.getDate(minX)
    const tsMax = this.getDate(maxX)

    const minD = this.formatDate(tsMin, 'yyyy MM dd HH mm ss fff').split(' ')
    const maxD = this.formatDate(tsMax, 'yyyy MM dd HH mm ss fff').split(' ')

    return {
      minMillisecond: parseInt(minD[6], 10),
      maxMillisecond: parseInt(maxD[6], 10),
      minSecond: parseInt(minD[5], 10),
      maxSecond: parseInt(maxD[5], 10),
      minMinute: parseInt(minD[4], 10),
      maxMinute: parseInt(maxD[4], 10),
      minHour: parseInt(minD[3], 10),
      maxHour: parseInt(maxD[3], 10),
      minDate: parseInt(minD[2], 10),
      maxDate: parseInt(maxD[2], 10),
      minMonth: parseInt(minD[1], 10) - 1,
      maxMonth: parseInt(maxD[1], 10) - 1,
      minYear: parseInt(minD[0], 10),
      maxYear: parseInt(maxD[0], 10),
    }
  }

  /**
   * @param {number} year
   */
  isLeapYear(year) {
    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
  }

  /**
   * @param {number} month
   * @param {number} year
   * @param {number} subtract
   */
  calculcateLastDaysOfMonth(month, year, subtract) {
    const days = this.determineDaysOfMonths(month, year)

    // whatever days we get, subtract the number of days asked
    return days - subtract
  }

  /**
   * @param {number} year
   */
  determineDaysOfYear(year) {
    let days = 365

    if (this.isLeapYear(year)) {
      days = 366
    }

    return days
  }

  /**
   * @param {number} year
   * @param {number} month
   * @param {number} date
   */
  determineRemainingDaysOfYear(year, month, date) {
    let dayOfYear = this.daysCntOfYear[month] + date
    if (month > 1 && this.isLeapYear(year)) dayOfYear++
    return dayOfYear
  }

  /**
   * @param {number} month
   * @param {number} year
   */
  determineDaysOfMonths(month, year) {
    let days = 30

    month = Utils.monthMod(month)

    switch (true) {
      case this.months30.indexOf(month) > -1:
        if (month === 2) {
          if (this.isLeapYear(year)) {
            days = 29
          } else {
            days = 28
          }
        }

        break

      case this.months31.indexOf(month) > -1:
        days = 31
        break

      default:
        days = 31
        break
    }

    return days
  }
}

export default DateTime
