<template>
  <bxs-picker
    ref="picker"
    :picker-data="columns"
    @change="onChange"
    />
</template>

<script>
import create from '../utils/create'
import bxsPicker from '../picker/picker'

function range(num, min, max) {
  return Math.min(Math.max(num, min), max)
}

const currentYear = new Date().getFullYear()
const isValidDate = date =>
  Object.prototype.toString.call(date) === '[object Date]' &&
  !Number.isNaN(date.getTime())

const isValidType = type =>
  ['datetime', 'date', 'time', 'year-month'].indexOf(type) !== -1

export default create({
  name: 'datetime-picker',
  components: {
    [bxsPicker.name]: bxsPicker,
  },
  props: {
    value: [Date, String],
    title: String,
    type: {
      type: String,
      default: 'datetime',
      validator: isValidType,
    },
    formatter: {
      type: Function,
      default: (type, value) => value,
    },
    minDate: {
      type: Date,
      default: () => new Date(currentYear - 10, 0, 1),
      validator: isValidDate,
    },
    maxDate: {
      type: Date,
      default: () => new Date(currentYear + 10, 11, 31),
      validator: isValidDate,
    },
    minHour: {
      type: Number,
      default: 0,
    },
    maxHour: {
      type: Number,
      default: 23,
    },
    minMinute: {
      type: Number,
      default: 0,
    },
    maxMinute: {
      type: Number,
      default: 59,
    },
    minSecond: {
      type: Number,
      default: 0,
    },
    maxSecond: {
      type: Number,
      default: 59,
    },
  },

  data() {
    return {
      innerValue: this.correctValue(this.value),
    }
  },

  watch: {
    value(val) {
      const v = this.correctValue(val)
      const isEqual =
        this.type === 'time'
          ? v === this.innerValue
          : v.valueOf() === this.innerValue.valueOf()
      if (!isEqual) this.innerValue = v
    },

    innerValue(val) {
      this.$emit('input', val)
    },

    columns() {
      this.updateColumnValue(this.innerValue)
    },
  },

  computed: {
    ranges() {
      if (this.type === 'time') {
        return [
          {
            type: 'hour',
            range: [this.minHour, this.maxHour],
          },
          {
            type: 'minute',
            range: [this.minMinute, this.maxMinute],
          },
          {
            type: 'second',
            range: [this.minSecond, this.maxSecond],
          },
        ]
      }

      const {
        maxYear,
        maxDate,
        maxMonth,
        maxHour,
        maxMinute,
        maxSecond,
      } = this.getBoundary('max', this.innerValue)
      const {
        minYear,
        minDate,
        minMonth,
        minHour,
        minMinute,
        minSecond,
      } = this.getBoundary('min', this.innerValue)

      const result = [
        {
          type: 'year',
          range: [minYear, maxYear],
        },
        {
          type: 'month',
          range: [minMonth, maxMonth],
        },
        {
          type: 'day',
          range: [minDate, maxDate],
        },
        {
          type: 'hour',
          range: [minHour, maxHour],
        },
        {
          type: 'minute',
          range: [minMinute, maxMinute],
        },
        {
          type: 'second',
          range: [minSecond, maxSecond],
        },
      ]

      if (this.type === 'date') result.splice(3, 3)
      if (this.type === 'year-month') result.splice(2, 4)
      return result
    },

    columns() {
      const results = this.ranges.map(item => {
        const rg = item.range
        const tp = item.type
        const values = this.times(rg[1] - rg[0] + 1, index => {
          let value = rg[0] + index
          value = value < 10 ? `0${value}` : `${value}`

          return this.iFormatter(tp, value)
        })

        return {
          values,
        }
      })

      return results
    },
  },

  methods: {
    pad(val) {
      return `00${val}`.slice(-2)
    },
    iFormatter(type, value) {
      return this.ranges.length > 3 ? value : this.formatter(type, value)
    },
    correctValue(value) {
      let val = value
      const isDateType = this.type !== 'time'
      if (isDateType && !isValidDate(val)) {
        val = this.minDate
      } else if (!val) {
        const { minHour } = this
        val = `${minHour > 9 ? minHour : `0${minHour}`}:00:00`
      }
      // time type
      if (!isDateType) {
        let [hour, minute, second] = val.split(':')
        hour = this.pad(range(hour, this.minHour, this.maxHour))
        minute = this.pad(range(minute, this.minMinute, this.maxMinute))
        second = this.pad(range(second, this.minSecond, this.maxSecond))

        return `${hour}:${minute}:${second}`
      }

      // date type
      val = range(val, this.minDate, this.maxDate)
      return new Date(val)
    },

    times(n, iteratee) {
      let index = 0
      const result = Array(n)

      while (index < n) {
        result[index] = iteratee(index)
        index += 1
      }
      return result
    },

    getBoundary(type, value) {
      const boundary = this[`${type}Date`]
      const year = boundary.getFullYear()
      let month = 1
      let date = 1
      let hour = 0
      let minute = 0
      let second = 0

      if (type === 'max') {
        month = 12
        date = this.getMonthEndDay(value.getFullYear(), value.getMonth() + 1)
        hour = 23
        minute = 59
        second = 59
      }

      if (value.getFullYear() === year) {
        month = boundary.getMonth() + 1
        if (value.getMonth() + 1 === month) {
          date = boundary.getDate()
          if (value.getDate() === date) {
            hour = boundary.getHours()
            if (value.getHours() === hour) {
              minute = boundary.getMinutes()
              if (value.getMinutes() === minute) {
                second = boundary.getSeconds()
              }
            }
          }
        }
      }

      return {
        [`${type}Year`]: year,
        [`${type}Month`]: month,
        [`${type}Date`]: date,
        [`${type}Hour`]: hour,
        [`${type}Minute`]: minute,
        [`${type}Second`]: second,
      }
    },

    getTrueValue(formattedValue) {
      let fv = formattedValue
      if (!fv) return null
      while (Number.isNaN(parseInt(fv, 10))) {
        fv = fv.slice(1)
      }
      return parseInt(fv, 10)
    },

    getMonthEndDay(year, month) {
      return 32 - new Date(year, month - 1, 32).getDate()
    },

    onConfirm() {
      this.$emit('confirm', this.innerValue)
    },

    onCancel() {
      this.$emit('cancel')
    },

    onChange(picker) {
      const values = picker.getValues()
      let value = null
      if (this.type === 'time') {
        value = values.join(':')
      } else {
        const year = this.getTrueValue(values[0])
        const month = this.getTrueValue(values[1])
        const maxDate = this.getMonthEndDay(year, month)
        let date = this.getTrueValue(values[2])
        if (this.type === 'year-month') {
          date = 1
        }
        date = date > maxDate ? maxDate : date
        let hour = 0
        let minute = 0
        let second = 0
        if (this.type === 'datetime') {
          hour = this.getTrueValue(values[3])
          minute = this.getTrueValue(values[4])
          second = this.getTrueValue(values[5])
        }
        value = new Date(year, month - 1, date, hour, minute, second)
      }
      value = this.correctValue(value)
      this.innerValue = value

      this.$nextTick(() => {
        this.$nextTick(() => {
          this.$emit('change', value)
        })
      })
    },

    updateColumnValue(value) {
      let values = []
      const { iFormatter, pad } = this

      if (this.type === 'time') {
        const currentValue = value.split(':')
        values = [
          iFormatter('hour', currentValue[0]),
          iFormatter('minute', currentValue[1]),
          iFormatter('second', currentValue[2]),
        ]
      } else {
        values = [
          iFormatter('year', `${value.getFullYear()}`),
          iFormatter('month', pad(value.getMonth() + 1)),
          iFormatter('day', pad(value.getDate())),
        ]

        if (this.type === 'datetime') {
          values.push(
            iFormatter('hour', pad(value.getHours())),
            iFormatter('minute', pad(value.getMinutes())),
            iFormatter('second', pad(value.getSeconds())),
          )
        }
        if (this.type === 'year-month') {
          values = values.slice(0, 2)
        }
      }

      this.$nextTick(() => {
        this.$refs.picker.setValues(values)
      })
    },
  },

  mounted() {
    this.updateColumnValue(this.innerValue)
  },
})
</script>
